Moved encodeHttpHeaderFieldParam method to HttpHeaders itself (including tests)
This commit also sets the test source encoding to UTF-8.
Issue: SPR-14547
(cherry picked from commit a8f7f75
)
This commit is contained in:
parent
9b91b9db8c
commit
696f687419
|
@ -110,11 +110,13 @@ configure(allprojects) { project ->
|
|||
compileJava {
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs += "-parameters"
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -1195,44 +1194,4 @@ public abstract class StringUtils {
|
|||
return arrayToDelimitedString(arr, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given header field param as describe in the rfc5987.
|
||||
* @param input the header field param
|
||||
* @param charset the charset of the header field param string
|
||||
* @return the encoded header field param
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5987">rfc5987</a>
|
||||
* @since 5.0
|
||||
*/
|
||||
public static String encodeHttpHeaderFieldParam(String input, Charset charset) {
|
||||
Assert.notNull(charset, "charset should not be null");
|
||||
if(Charset.forName("US-ASCII").equals(charset)) {
|
||||
return input;
|
||||
}
|
||||
Assert.isTrue(Charset.forName("UTF-8").equals(charset) || Charset.forName("ISO-8859-1").equals(charset),
|
||||
"charset should be UTF-8 or ISO-8859-1");
|
||||
final byte[] source = input.getBytes(charset);
|
||||
final int len = source.length;
|
||||
final 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 == '~';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
@ -628,8 +627,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-3671
|
||||
@Test
|
||||
@Test // SPR-3671
|
||||
public void testParseLocaleWithMultiValuedVariant() throws Exception {
|
||||
final String variant = "proper_northern";
|
||||
final String localeString = "en_GB_" + variant;
|
||||
|
@ -637,8 +635,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-3671
|
||||
@Test
|
||||
@Test // SPR-3671
|
||||
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() throws Exception {
|
||||
final String variant = "proper northern";
|
||||
final String localeString = "en GB " + variant;
|
||||
|
@ -646,8 +643,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-3671
|
||||
@Test
|
||||
@Test // SPR-3671
|
||||
public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() throws Exception {
|
||||
final String variant = "proper northern";
|
||||
final String localeString = "en_GB_" + variant;
|
||||
|
@ -655,8 +651,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-3671
|
||||
@Test
|
||||
@Test // SPR-3671
|
||||
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
|
||||
final String variant = "proper northern";
|
||||
final String localeString = "en GB " + variant; // lots of whitespace
|
||||
|
@ -664,8 +659,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-3671
|
||||
@Test
|
||||
@Test // SPR-3671
|
||||
public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
|
||||
final String variant = "proper_northern";
|
||||
final String localeString = "en_GB_____" + variant; // lots of underscores
|
||||
|
@ -673,8 +667,7 @@ public class StringUtilsTests {
|
|||
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-7779
|
||||
@Test
|
||||
@Test // SPR-7779
|
||||
public void testParseLocaleWithInvalidCharacters() {
|
||||
try {
|
||||
StringUtils.parseLocaleString("%0D%0AContent-length:30%0D%0A%0D%0A%3Cscript%3Ealert%28123%29%3C/script%3E");
|
||||
|
@ -685,15 +678,13 @@ public class StringUtilsTests {
|
|||
}
|
||||
}
|
||||
|
||||
// SPR-9420
|
||||
@Test
|
||||
@Test // SPR-9420
|
||||
public void testParseLocaleWithSameLowercaseTokenForLanguageAndCountry() {
|
||||
assertEquals("tr_TR", StringUtils.parseLocaleString("tr_tr").toString());
|
||||
assertEquals("bg_BG_vnt", StringUtils.parseLocaleString("bg_bg_vnt").toString());
|
||||
}
|
||||
|
||||
// SPR-11806
|
||||
@Test
|
||||
@Test // SPR-11806
|
||||
public void testParseLocaleWithVariantContainingCountryCode() {
|
||||
String variant = "GBtest";
|
||||
String localeString = "en_GB_" + variant;
|
||||
|
@ -701,19 +692,4 @@ public class StringUtilsTests {
|
|||
assertEquals("Variant containing country code not extracted correctly", variant, locale.getVariant());
|
||||
}
|
||||
|
||||
// SPR-14547
|
||||
@Test
|
||||
public void encodeHttpHeaderFieldParam() {
|
||||
String result = StringUtils.encodeHttpHeaderFieldParam("test.txt", Charset.forName("US-ASCII"));
|
||||
assertEquals("test.txt", result);
|
||||
|
||||
result = StringUtils.encodeHttpHeaderFieldParam("中文.txt", Charset.forName("UTF-8"));
|
||||
assertEquals("UTF-8''%E4%B8%AD%E6%96%87.txt", result);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void encodeHttpHeaderFieldParamInvalidCharset() {
|
||||
StringUtils.encodeHttpHeaderFieldParam("test", Charset.forName("UTF-16"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -677,13 +677,14 @@ public class HttpHeaders implements MultiValueMap<String, String>, 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.
|
||||
* <p>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 <a href="https://tools.ietf.org/html/rfc7230#section-3.2.4">rfc7230 Section 3.2.4</a>
|
||||
* @since 5.0
|
||||
* @since 4.3.3
|
||||
* @see #setContentDispositionFormData(String, String)
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.4">RFC 7230 Section 3.2.4</a>
|
||||
*/
|
||||
public void setContentDispositionFormData(String name, String filename, Charset charset) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
|
@ -696,7 +697,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
|
|||
}
|
||||
else {
|
||||
builder.append("; filename*=");
|
||||
builder.append(StringUtils.encodeHttpHeaderFieldParam(filename, charset));
|
||||
builder.append(encodeHeaderFieldParam(filename, charset));
|
||||
}
|
||||
}
|
||||
set(CONTENT_DISPOSITION, builder.toString());
|
||||
|
@ -1302,4 +1303,45 @@ public class HttpHeaders implements MultiValueMap<String, String>, 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 <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
|
||||
*/
|
||||
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 (charset.name().equals("US-ASCII")) {
|
||||
return input;
|
||||
}
|
||||
Assert.isTrue(charset.name().equals("UTF-8") || charset.name().equals("ISO-8859-1"),
|
||||
"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 == '~';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -409,4 +409,18 @@ public class HttpHeadersTests {
|
|||
assertEquals(HttpMethod.POST, headers.getAccessControlRequestMethod());
|
||||
}
|
||||
|
||||
@Test // SPR-14547
|
||||
public void encodeHeaderFieldParam() {
|
||||
String result = HttpHeaders.encodeHeaderFieldParam("test.txt", Charset.forName("US-ASCII"));
|
||||
assertEquals("test.txt", result);
|
||||
|
||||
result = HttpHeaders.encodeHeaderFieldParam("中文.txt", Charset.forName("UTF-8"));
|
||||
assertEquals("UTF-8''%E4%B8%AD%E6%96%87.txt", result);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void encodeHeaderFieldParamInvalidCharset() {
|
||||
HttpHeaders.encodeHeaderFieldParam("test", Charset.forName("UTF-16"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <a href="http://groovy-lang.org/templating.html#_the_markuptemplateengine">
|
||||
* Groovy Markup Template engine documentation</a>
|
||||
* Groovy Markup Template engine documentation</a>
|
||||
*/
|
||||
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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -64,6 +62,7 @@ public class GroovyMarkupViewTests {
|
|||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void missingGroovyMarkupConfig() throws Exception {
|
||||
GroovyMarkupView view = new GroovyMarkupView();
|
||||
|
@ -82,7 +81,6 @@ public class GroovyMarkupViewTests {
|
|||
|
||||
@Test
|
||||
public void customTemplateEngine() throws Exception {
|
||||
|
||||
GroovyMarkupView view = new GroovyMarkupView();
|
||||
view.setTemplateEngine(new TestTemplateEngine());
|
||||
view.setApplicationContext(this.webAppContext);
|
||||
|
@ -95,9 +93,7 @@ public class GroovyMarkupViewTests {
|
|||
|
||||
@Test
|
||||
public void detectTemplateEngine() throws Exception {
|
||||
|
||||
GroovyMarkupView view = new GroovyMarkupView();
|
||||
|
||||
view.setTemplateEngine(new TestTemplateEngine());
|
||||
view.setApplicationContext(this.webAppContext);
|
||||
|
||||
|
@ -109,35 +105,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<String, Object> model = new HashMap<>();
|
||||
model.put("name", "Spring");
|
||||
MockHttpServletResponse response = renderViewWithModel("test.tpl", model, Locale.US);
|
||||
|
@ -155,7 +146,7 @@ public class GroovyMarkupViewTests {
|
|||
assertEquals("<p>Include German</p><p>Hallo Spring</p>", response.getContentAsString());
|
||||
|
||||
response = renderViewWithModel("i18n.tpl", model, new Locale("es"));
|
||||
assertEquals("<p>Include Default</p><p>¡hola Spring</p>", response.getContentAsString());
|
||||
assertEquals("<p>Include Default</p><p>Hola Spring</p>", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -166,30 +157,30 @@ public class GroovyMarkupViewTests {
|
|||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model, Locale locale) throws Exception {
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> 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 +188,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 +204,5 @@ public class GroovyMarkupViewTests {
|
|||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
include template: 'includes/include.tpl'
|
||||
p('¡hola Spring')
|
||||
p('Hola Spring')
|
Loading…
Reference in New Issue