Skip to content

Commit 696f687

Browse files
committed
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)
1 parent 9b91b9d commit 696f687

File tree

8 files changed

+99
-112
lines changed

8 files changed

+99
-112
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,13 @@ configure(allprojects) { project ->
110110
compileJava {
111111
sourceCompatibility = 1.6
112112
targetCompatibility = 1.6
113+
options.encoding = 'UTF-8'
113114
}
114115

115116
compileTestJava {
116117
sourceCompatibility = 1.8
117118
targetCompatibility = 1.8
119+
options.encoding = 'UTF-8'
118120
options.compilerArgs += "-parameters"
119121
}
120122

spring-core/src/main/java/org/springframework/util/StringUtils.java

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.util;
1818

19-
import java.nio.charset.Charset;
2019
import java.util.ArrayList;
2120
import java.util.Arrays;
2221
import java.util.Collection;
@@ -1195,44 +1194,4 @@ public static String arrayToCommaDelimitedString(Object[] arr) {
11951194
return arrayToDelimitedString(arr, ",");
11961195
}
11971196

1198-
/**
1199-
* Encode the given header field param as describe in the rfc5987.
1200-
* @param input the header field param
1201-
* @param charset the charset of the header field param string
1202-
* @return the encoded header field param
1203-
* @see <a href="https://tools.ietf.org/html/rfc5987">rfc5987</a>
1204-
* @since 5.0
1205-
*/
1206-
public static String encodeHttpHeaderFieldParam(String input, Charset charset) {
1207-
Assert.notNull(charset, "charset should not be null");
1208-
if(Charset.forName("US-ASCII").equals(charset)) {
1209-
return input;
1210-
}
1211-
Assert.isTrue(Charset.forName("UTF-8").equals(charset) || Charset.forName("ISO-8859-1").equals(charset),
1212-
"charset should be UTF-8 or ISO-8859-1");
1213-
final byte[] source = input.getBytes(charset);
1214-
final int len = source.length;
1215-
final StringBuilder sb = new StringBuilder(len << 1);
1216-
sb.append(charset.name());
1217-
sb.append("''");
1218-
for (byte b : source) {
1219-
if (isRFC5987AttrChar(b)) {
1220-
sb.append((char) b);
1221-
}
1222-
else {
1223-
sb.append('%');
1224-
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
1225-
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
1226-
sb.append(hex1);
1227-
sb.append(hex2);
1228-
}
1229-
}
1230-
return sb.toString();
1231-
}
1232-
1233-
private static boolean isRFC5987AttrChar(byte c) {
1234-
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
1235-
|| c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-'
1236-
|| c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
1237-
}
12381197
}

spring-core/src/test/java/org/springframework/util/StringUtilsTests.java

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.util;
1818

19-
import java.nio.charset.Charset;
2019
import java.util.Arrays;
2120
import java.util.Locale;
2221
import java.util.Properties;
@@ -628,53 +627,47 @@ public void testParseLocaleWithMultiSpecialCharactersInVariant() throws Exceptio
628627
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
629628
}
630629

631-
// SPR-3671
632-
@Test
630+
@Test // SPR-3671
633631
public void testParseLocaleWithMultiValuedVariant() throws Exception {
634632
final String variant = "proper_northern";
635633
final String localeString = "en_GB_" + variant;
636634
Locale locale = StringUtils.parseLocaleString(localeString);
637635
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
638636
}
639637

640-
// SPR-3671
641-
@Test
638+
@Test // SPR-3671
642639
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() throws Exception {
643640
final String variant = "proper northern";
644641
final String localeString = "en GB " + variant;
645642
Locale locale = StringUtils.parseLocaleString(localeString);
646643
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
647644
}
648645

649-
// SPR-3671
650-
@Test
646+
@Test // SPR-3671
651647
public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() throws Exception {
652648
final String variant = "proper northern";
653649
final String localeString = "en_GB_" + variant;
654650
Locale locale = StringUtils.parseLocaleString(localeString);
655651
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
656652
}
657653

658-
// SPR-3671
659-
@Test
654+
@Test // SPR-3671
660655
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
661656
final String variant = "proper northern";
662657
final String localeString = "en GB " + variant; // lots of whitespace
663658
Locale locale = StringUtils.parseLocaleString(localeString);
664659
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
665660
}
666661

667-
// SPR-3671
668-
@Test
662+
@Test // SPR-3671
669663
public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
670664
final String variant = "proper_northern";
671665
final String localeString = "en_GB_____" + variant; // lots of underscores
672666
Locale locale = StringUtils.parseLocaleString(localeString);
673667
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
674668
}
675669

676-
// SPR-7779
677-
@Test
670+
@Test // SPR-7779
678671
public void testParseLocaleWithInvalidCharacters() {
679672
try {
680673
StringUtils.parseLocaleString("%0D%0AContent-length:30%0D%0A%0D%0A%3Cscript%3Ealert%28123%29%3C/script%3E");
@@ -685,35 +678,18 @@ public void testParseLocaleWithInvalidCharacters() {
685678
}
686679
}
687680

688-
// SPR-9420
689-
@Test
681+
@Test // SPR-9420
690682
public void testParseLocaleWithSameLowercaseTokenForLanguageAndCountry() {
691683
assertEquals("tr_TR", StringUtils.parseLocaleString("tr_tr").toString());
692684
assertEquals("bg_BG_vnt", StringUtils.parseLocaleString("bg_bg_vnt").toString());
693685
}
694686

695-
// SPR-11806
696-
@Test
687+
@Test // SPR-11806
697688
public void testParseLocaleWithVariantContainingCountryCode() {
698689
String variant = "GBtest";
699690
String localeString = "en_GB_" + variant;
700691
Locale locale = StringUtils.parseLocaleString(localeString);
701692
assertEquals("Variant containing country code not extracted correctly", variant, locale.getVariant());
702693
}
703694

704-
// SPR-14547
705-
@Test
706-
public void encodeHttpHeaderFieldParam() {
707-
String result = StringUtils.encodeHttpHeaderFieldParam("test.txt", Charset.forName("US-ASCII"));
708-
assertEquals("test.txt", result);
709-
710-
result = StringUtils.encodeHttpHeaderFieldParam("中文.txt", Charset.forName("UTF-8"));
711-
assertEquals("UTF-8''%E4%B8%AD%E6%96%87.txt", result);
712-
}
713-
714-
@Test(expected = IllegalArgumentException.class)
715-
public void encodeHttpHeaderFieldParamInvalidCharset() {
716-
StringUtils.encodeHttpHeaderFieldParam("test", Charset.forName("UTF-16"));
717-
}
718-
719695
}

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -677,13 +677,14 @@ public void setContentDispositionFormData(String name, String filename) {
677677

678678
/**
679679
* Set the (new) value of the {@code Content-Disposition} header
680-
* for {@code form-data}, optionally encoding the filename using the rfc5987.
680+
* for {@code form-data}, optionally encoding the filename using the RFC 5987.
681681
* <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported.
682682
* @param name the control name
683683
* @param filename the filename (may be {@code null})
684684
* @param charset the charset used for the filename (may be {@code null})
685-
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.4">rfc7230 Section 3.2.4</a>
686-
* @since 5.0
685+
* @since 4.3.3
686+
* @see #setContentDispositionFormData(String, String)
687+
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.4">RFC 7230 Section 3.2.4</a>
687688
*/
688689
public void setContentDispositionFormData(String name, String filename, Charset charset) {
689690
Assert.notNull(name, "'name' must not be null");
@@ -696,7 +697,7 @@ public void setContentDispositionFormData(String name, String filename, Charset
696697
}
697698
else {
698699
builder.append("; filename*=");
699-
builder.append(StringUtils.encodeHttpHeaderFieldParam(filename, charset));
700+
builder.append(encodeHeaderFieldParam(filename, charset));
700701
}
701702
}
702703
set(CONTENT_DISPOSITION, builder.toString());
@@ -1302,4 +1303,45 @@ public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
13021303
return new HttpHeaders(headers, true);
13031304
}
13041305

1306+
/**
1307+
* Encode the given header field param as describe in RFC 5987.
1308+
* @param input the header field param
1309+
* @param charset the charset of the header field param string
1310+
* @return the encoded header field param
1311+
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
1312+
*/
1313+
static String encodeHeaderFieldParam(String input, Charset charset) {
1314+
Assert.notNull(input, "Input String should not be null");
1315+
Assert.notNull(charset, "Charset should not be null");
1316+
if (charset.name().equals("US-ASCII")) {
1317+
return input;
1318+
}
1319+
Assert.isTrue(charset.name().equals("UTF-8") || charset.name().equals("ISO-8859-1"),
1320+
"Charset should be UTF-8 or ISO-8859-1");
1321+
byte[] source = input.getBytes(charset);
1322+
int len = source.length;
1323+
StringBuilder sb = new StringBuilder(len << 1);
1324+
sb.append(charset.name());
1325+
sb.append("''");
1326+
for (byte b : source) {
1327+
if (isRFC5987AttrChar(b)) {
1328+
sb.append((char) b);
1329+
}
1330+
else {
1331+
sb.append('%');
1332+
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
1333+
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
1334+
sb.append(hex1);
1335+
sb.append(hex2);
1336+
}
1337+
}
1338+
return sb.toString();
1339+
}
1340+
1341+
private static boolean isRFC5987AttrChar(byte c) {
1342+
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
1343+
c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-' ||
1344+
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
1345+
}
1346+
13051347
}

spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,18 @@ public void accessControlRequestMethod() {
409409
assertEquals(HttpMethod.POST, headers.getAccessControlRequestMethod());
410410
}
411411

412+
@Test // SPR-14547
413+
public void encodeHeaderFieldParam() {
414+
String result = HttpHeaders.encodeHeaderFieldParam("test.txt", Charset.forName("US-ASCII"));
415+
assertEquals("test.txt", result);
416+
417+
result = HttpHeaders.encodeHeaderFieldParam("中文.txt", Charset.forName("UTF-8"));
418+
assertEquals("UTF-8''%E4%B8%AD%E6%96%87.txt", result);
419+
}
420+
421+
@Test(expected = IllegalArgumentException.class)
422+
public void encodeHeaderFieldParamInvalidCharset() {
423+
HttpHeaders.encodeHeaderFieldParam("test", Charset.forName("UTF-16"));
424+
}
425+
412426
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@
4545
* @see GroovyMarkupViewResolver
4646
* @see GroovyMarkupConfigurer
4747
* @see <a href="http://groovy-lang.org/templating.html#_the_markuptemplateengine">
48-
* Groovy Markup Template engine documentation</a>
48+
* Groovy Markup Template engine documentation</a>
4949
*/
5050
public class GroovyMarkupView extends AbstractTemplateView {
5151

@@ -63,17 +63,6 @@ public void setTemplateEngine(MarkupTemplateEngine engine) {
6363
this.engine = engine;
6464
}
6565

66-
@Override
67-
public boolean checkResource(Locale locale) throws Exception {
68-
try {
69-
this.engine.resolveTemplate(getUrl());
70-
}
71-
catch (IOException exception) {
72-
return false;
73-
}
74-
return true;
75-
}
76-
7766
/**
7867
* Invoked at startup.
7968
* If no {@link #setTemplateEngine(MarkupTemplateEngine) templateEngine} has
@@ -107,6 +96,17 @@ protected MarkupTemplateEngine autodetectMarkupTemplateEngine() throws BeansExce
10796
}
10897

10998

99+
@Override
100+
public boolean checkResource(Locale locale) throws Exception {
101+
try {
102+
this.engine.resolveTemplate(getUrl());
103+
}
104+
catch (IOException ex) {
105+
return false;
106+
}
107+
return true;
108+
}
109+
110110
@Override
111111
protected void renderMergedTemplateModel(Map<String, Object> model,
112112
HttpServletRequest request, HttpServletResponse response) throws Exception {
@@ -125,8 +125,9 @@ protected Template getTemplate(String viewUrl) throws Exception {
125125
}
126126
catch (ClassNotFoundException ex) {
127127
Throwable cause = (ex.getCause() != null ? ex.getCause() : ex);
128-
throw new NestedServletException("Could not find class while rendering Groovy Markup view with name '" +
129-
getUrl() + "': " + ex.getMessage() + "'", cause);
128+
throw new NestedServletException(
129+
"Could not find class while rendering Groovy Markup view with name '" +
130+
getUrl() + "': " + ex.getMessage() + "'", cause);
130131
}
131132
}
132133

0 commit comments

Comments
 (0)