, Serializable
/**
* Set the (new) value of the {@code Content-Disposition} header
- * for {@code form-data}, optionally encoding the filename using the rfc5987.
+ * for {@code form-data}, optionally encoding the filename using the RFC 5987.
* Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported.
* @param name the control name
* @param filename the filename (may be {@code null})
* @param charset the charset used for the filename (may be {@code null})
- * @see rfc7230 Section 3.2.4
- * @since 5.0
+ * @since 4.3.3
+ * @see #setContentDispositionFormData(String, String)
+ * @see RFC 7230 Section 3.2.4
*/
public void setContentDispositionFormData(String name, String filename, Charset charset) {
Assert.notNull(name, "'name' must not be null");
@@ -698,7 +699,7 @@ public class HttpHeaders implements MultiValueMap, Serializable
}
else {
builder.append("; filename*=");
- builder.append(StringUtils.encodeHttpHeaderFieldParam(filename, charset));
+ builder.append(encodeHeaderFieldParam(filename, charset));
}
}
set(CONTENT_DISPOSITION, builder.toString());
@@ -1345,4 +1346,45 @@ public class HttpHeaders implements MultiValueMap, Serializable
return new HttpHeaders(headers, true);
}
+ /**
+ * Encode the given header field param as describe in RFC 5987.
+ * @param input the header field param
+ * @param charset the charset of the header field param string
+ * @return the encoded header field param
+ * @see RFC 5987
+ */
+ static String encodeHeaderFieldParam(String input, Charset charset) {
+ Assert.notNull(input, "Input String should not be null");
+ Assert.notNull(charset, "Charset should not be null");
+ if (StandardCharsets.US_ASCII.equals(charset)) {
+ return input;
+ }
+ Assert.isTrue(StandardCharsets.UTF_8.equals(charset) || StandardCharsets.ISO_8859_1.equals(charset),
+ "Charset should be UTF-8 or ISO-8859-1");
+ byte[] source = input.getBytes(charset);
+ int len = source.length;
+ StringBuilder sb = new StringBuilder(len << 1);
+ sb.append(charset.name());
+ sb.append("''");
+ for (byte b : source) {
+ if (isRFC5987AttrChar(b)) {
+ sb.append((char) b);
+ }
+ else {
+ sb.append('%');
+ char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+ char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+ sb.append(hex1);
+ sb.append(hex2);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static boolean isRFC5987AttrChar(byte c) {
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-' ||
+ c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
index c6464553a6..b28f4d8bc9 100644
--- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
+++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
@@ -150,7 +150,6 @@ public class HttpHeadersTests {
headers.setHost(host);
assertEquals("Invalid Host header", host, headers.getHost());
assertEquals("Invalid Host header", "localhost:8080", headers.getFirst("Host"));
-
}
@Test
@@ -159,7 +158,6 @@ public class HttpHeadersTests {
headers.setHost(host);
assertEquals("Invalid Host header", host, headers.getHost());
assertEquals("Invalid Host header", "localhost", headers.getFirst("Host"));
-
}
@Test(expected = IllegalArgumentException.class)
@@ -429,4 +427,18 @@ public class HttpHeadersTests {
assertEquals(HttpMethod.POST, headers.getAccessControlRequestMethod());
}
+ @Test // SPR-14547
+ public void encodeHeaderFieldParam() {
+ String result = HttpHeaders.encodeHeaderFieldParam("test.txt", StandardCharsets.US_ASCII);
+ assertEquals("test.txt", result);
+
+ result = HttpHeaders.encodeHeaderFieldParam("中文.txt", StandardCharsets.UTF_8);
+ assertEquals("UTF-8''%E4%B8%AD%E6%96%87.txt", result);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void encodeHeaderFieldParamInvalidCharset() {
+ HttpHeaders.encodeHeaderFieldParam("test", StandardCharsets.UTF_16);
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
index 38eda9811f..e3007c34a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ import org.springframework.web.util.NestedServletException;
* @see GroovyMarkupViewResolver
* @see GroovyMarkupConfigurer
* @see
- * Groovy Markup Template engine documentation
+ * Groovy Markup Template engine documentation
*/
public class GroovyMarkupView extends AbstractTemplateView {
@@ -63,17 +63,6 @@ public class GroovyMarkupView extends AbstractTemplateView {
this.engine = engine;
}
- @Override
- public boolean checkResource(Locale locale) throws Exception {
- try {
- this.engine.resolveTemplate(getUrl());
- }
- catch (IOException exception) {
- return false;
- }
- return true;
- }
-
/**
* Invoked at startup.
* If no {@link #setTemplateEngine(MarkupTemplateEngine) templateEngine} has
@@ -107,6 +96,17 @@ public class GroovyMarkupView extends AbstractTemplateView {
}
+ @Override
+ public boolean checkResource(Locale locale) throws Exception {
+ try {
+ this.engine.resolveTemplate(getUrl());
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ return true;
+ }
+
@Override
protected void renderMergedTemplateModel(Map model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
@@ -125,8 +125,9 @@ public class GroovyMarkupView extends AbstractTemplateView {
}
catch (ClassNotFoundException ex) {
Throwable cause = (ex.getCause() != null ? ex.getCause() : ex);
- throw new NestedServletException("Could not find class while rendering Groovy Markup view with name '" +
- getUrl() + "': " + ex.getMessage() + "'", cause);
+ throw new NestedServletException(
+ "Could not find class while rendering Groovy Markup view with name '" +
+ getUrl() + "': " + ex.getMessage() + "'", cause);
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java
index d276be0313..63f2bb0257 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java
@@ -15,9 +15,7 @@
*/
package org.springframework.web.servlet.view.groovy;
-import java.io.IOException;
import java.io.Reader;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -27,7 +25,6 @@ import groovy.text.Template;
import groovy.text.TemplateEngine;
import groovy.text.markup.MarkupTemplateEngine;
import groovy.text.markup.TemplateConfiguration;
-import org.codehaus.groovy.control.CompilationFailedException;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -57,6 +54,7 @@ public class GroovyMarkupViewTests {
private ServletContext servletContext;
+
@Before
public void setup() {
this.webAppContext = mock(WebApplicationContext.class);
@@ -82,7 +80,6 @@ public class GroovyMarkupViewTests {
@Test
public void customTemplateEngine() throws Exception {
-
GroovyMarkupView view = new GroovyMarkupView();
view.setTemplateEngine(new TestTemplateEngine());
view.setApplicationContext(this.webAppContext);
@@ -95,9 +92,7 @@ public class GroovyMarkupViewTests {
@Test
public void detectTemplateEngine() throws Exception {
-
GroovyMarkupView view = new GroovyMarkupView();
-
view.setTemplateEngine(new TestTemplateEngine());
view.setApplicationContext(this.webAppContext);
@@ -109,35 +104,30 @@ public class GroovyMarkupViewTests {
@Test
public void checkResource() throws Exception {
-
GroovyMarkupView view = createViewWithUrl("test.tpl");
assertTrue(view.checkResource(Locale.US));
}
@Test
public void checkMissingResource() throws Exception {
-
GroovyMarkupView view = createViewWithUrl("missing.tpl");
assertFalse(view.checkResource(Locale.US));
}
@Test
public void checkI18nResource() throws Exception {
-
GroovyMarkupView view = createViewWithUrl("i18n.tpl");
assertTrue(view.checkResource(Locale.FRENCH));
}
@Test
public void checkI18nResourceMissingLocale() throws Exception {
-
GroovyMarkupView view = createViewWithUrl("i18n.tpl");
assertTrue(view.checkResource(Locale.CHINESE));
}
@Test
public void renderMarkupTemplate() throws Exception {
-
Map model = new HashMap<>();
model.put("name", "Spring");
MockHttpServletResponse response = renderViewWithModel("test.tpl", model, Locale.US);
@@ -155,7 +145,7 @@ public class GroovyMarkupViewTests {
assertEquals("Include German
Hallo Spring
", response.getContentAsString());
response = renderViewWithModel("i18n.tpl", model, new Locale("es"));
- assertEquals("Include Default
¡hola Spring
", response.getContentAsString());
+ assertEquals("Include Default
Hola Spring
", response.getContentAsString());
}
@Test
@@ -166,30 +156,30 @@ public class GroovyMarkupViewTests {
response.getContentAsString());
}
- private MockHttpServletResponse renderViewWithModel(String viewUrl, Map model, Locale locale) throws Exception {
+ private MockHttpServletResponse renderViewWithModel(String viewUrl, Map model, Locale locale) throws Exception {
GroovyMarkupView view = createViewWithUrl(viewUrl);
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();
- request.setPreferredLocales(Arrays.asList(locale));
+ request.addPreferredLocale(locale);
LocaleContextHolder.setLocale(locale);
view.renderMergedTemplateModel(model, request, response);
return response;
}
private GroovyMarkupView createViewWithUrl(String viewUrl) throws Exception {
-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(GroovyMarkupConfiguration.class);
ctx.refresh();
GroovyMarkupView view = new GroovyMarkupView();
- view.setApplicationContext(ctx);
view.setUrl(viewUrl);
+ view.setApplicationContext(ctx);
view.afterPropertiesSet();
return view;
}
+
public class TestTemplateEngine extends MarkupTemplateEngine {
public TestTemplateEngine() {
@@ -197,11 +187,12 @@ public class GroovyMarkupViewTests {
}
@Override
- public Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
+ public Template createTemplate(Reader reader) {
return null;
}
}
+
@Configuration
static class GroovyMarkupConfiguration {
@@ -212,4 +203,5 @@ public class GroovyMarkupViewTests {
return configurer;
}
}
+
}
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl
index 2d52884bde..169951e0d6 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl
@@ -1,2 +1,2 @@
include template: 'includes/include.tpl'
-p('¡hola Spring')
\ No newline at end of file
+p('Hola Spring')
\ No newline at end of file