Skip to content

Commit d927d31

Browse files
committed
Remove reflection from ContentDispositionTests
Also minor refactoring in decoding in order to tolerate the absence of a charset and treat as US_ASCII. See gh-23485
1 parent c975800 commit d927d31

File tree

2 files changed

+61
-82
lines changed

2 files changed

+61
-82
lines changed

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

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public String toString() {
208208
}
209209
else {
210210
sb.append("; filename*=");
211-
sb.append(encodeHeaderFieldParam(this.filename, this.charset));
211+
sb.append(encodeFilename(this.filename, this.charset));
212212
}
213213
}
214214
if (this.size != null) {
@@ -274,15 +274,23 @@ public static ContentDisposition parse(String contentDisposition) {
274274
String attribute = part.substring(0, eqIndex);
275275
String value = (part.startsWith("\"", eqIndex + 1) && part.endsWith("\"") ?
276276
part.substring(eqIndex + 2, part.length() - 1) :
277-
part.substring(eqIndex + 1, part.length()));
277+
part.substring(eqIndex + 1));
278278
if (attribute.equals("name") ) {
279279
name = value;
280280
}
281281
else if (attribute.equals("filename*") ) {
282-
filename = decodeHeaderFieldParam(value);
283-
charset = Charset.forName(value.substring(0, value.indexOf('\'')));
284-
Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
285-
"Charset should be UTF-8 or ISO-8859-1");
282+
int idx1 = value.indexOf('\'');
283+
int idx2 = value.indexOf('\'', idx1 + 1);
284+
if (idx1 != -1 && idx2 != -1) {
285+
charset = Charset.forName(value.substring(0, idx1));
286+
Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
287+
"Charset should be UTF-8 or ISO-8859-1");
288+
filename = decodeFilename(value.substring(idx2 + 1), charset);
289+
}
290+
else {
291+
// US ASCII
292+
filename = decodeFilename(value, StandardCharsets.US_ASCII);
293+
}
286294
}
287295
else if (attribute.equals("filename") && (filename == null)) {
288296
filename = value;
@@ -362,22 +370,15 @@ else if (!escaped && ch == '"') {
362370
/**
363371
* Decode the given header field param as described in RFC 5987.
364372
* <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported.
365-
* @param input the header field param
373+
* @param filename the filename
374+
* @param charset the charset for the filename
366375
* @return the encoded header field param
367376
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
368377
*/
369-
private static String decodeHeaderFieldParam(String input) {
370-
Assert.notNull(input, "Input String should not be null");
371-
int firstQuoteIndex = input.indexOf('\'');
372-
int secondQuoteIndex = input.indexOf('\'', firstQuoteIndex + 1);
373-
// US_ASCII
374-
if (firstQuoteIndex == -1 || secondQuoteIndex == -1) {
375-
return input;
376-
}
377-
Charset charset = Charset.forName(input.substring(0, firstQuoteIndex));
378-
Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
379-
"Charset should be UTF-8 or ISO-8859-1");
380-
byte[] value = input.substring(secondQuoteIndex + 1, input.length()).getBytes(charset);
378+
private static String decodeFilename(String filename, Charset charset) {
379+
Assert.notNull(filename, "'input' String` should not be null");
380+
Assert.notNull(charset, "'charset' should not be null");
381+
byte[] value = filename.getBytes(charset);
381382
ByteArrayOutputStream bos = new ByteArrayOutputStream();
382383
int index = 0;
383384
while (index < value.length) {
@@ -417,7 +418,7 @@ private static boolean isRFC5987AttrChar(byte c) {
417418
* @return the encoded header field param
418419
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
419420
*/
420-
private static String encodeHeaderFieldParam(String input, Charset charset) {
421+
private static String encodeFilename(String input, Charset charset) {
421422
Assert.notNull(input, "Input String should not be null");
422423
Assert.notNull(charset, "Charset should not be null");
423424
if (StandardCharsets.US_ASCII.equals(charset)) {

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

Lines changed: 40 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@
1616

1717
package org.springframework.http;
1818

19-
import java.lang.reflect.Method;
20-
import java.nio.charset.Charset;
2119
import java.nio.charset.StandardCharsets;
2220
import java.time.ZonedDateTime;
2321
import java.time.format.DateTimeFormatter;
2422

2523
import org.junit.jupiter.api.Test;
2624

27-
import org.springframework.util.ReflectionUtils;
28-
2925
import static org.assertj.core.api.Assertions.assertThat;
3026
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
3127

@@ -74,6 +70,30 @@ public void parseEncodedFilename() {
7470
.build());
7571
}
7672

73+
@Test
74+
public void parseEncodedFilenameWithoutCharset() {
75+
assertThat(parse("form-data; name=\"name\"; filename*=test.txt"))
76+
.isEqualTo(ContentDisposition.builder("form-data")
77+
.name("name")
78+
.filename("test.txt")
79+
.build());
80+
}
81+
82+
@Test
83+
public void parseEncodedFilenameWithInvalidCharset() {
84+
assertThatIllegalArgumentException()
85+
.isThrownBy(() -> parse("form-data; name=\"name\"; filename*=UTF-16''test.txt"));
86+
}
87+
88+
@Test
89+
public void parseEncodedFilenameWithInvalidName() {
90+
assertThatIllegalArgumentException()
91+
.isThrownBy(() -> parse("form-data; name=\"name\"; filename*=UTF-8''%A"));
92+
93+
assertThatIllegalArgumentException()
94+
.isThrownBy(() -> parse("form-data; name=\"name\"; filename*=UTF-8''%A.txt"));
95+
}
96+
7797
@Test // gh-23077
7898
public void parseWithEscapedQuote() {
7999
assertThat(parse("form-data; name=\"file\"; filename=\"\\\"The Twilight Zone\\\".txt\"; size=123"))
@@ -147,7 +167,7 @@ private static ContentDisposition parse(String input) {
147167

148168

149169
@Test
150-
public void headerValue() {
170+
public void format() {
151171
assertThat(
152172
ContentDisposition.builder("form-data")
153173
.name("foo")
@@ -158,7 +178,7 @@ public void headerValue() {
158178
}
159179

160180
@Test
161-
public void headerValueWithEncodedFilename() {
181+
public void formatWithEncodedFilename() {
162182
assertThat(
163183
ContentDisposition.builder("form-data")
164184
.name("name")
@@ -167,67 +187,25 @@ public void headerValueWithEncodedFilename() {
167187
.isEqualTo("form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt");
168188
}
169189

170-
@Test // SPR-14547
171-
public void encodeHeaderFieldParam() {
172-
Method encode = ReflectionUtils.findMethod(ContentDisposition.class,
173-
"encodeHeaderFieldParam", String.class, Charset.class);
174-
ReflectionUtils.makeAccessible(encode);
175-
176-
String result = (String)ReflectionUtils.invokeMethod(encode, null, "test.txt",
177-
StandardCharsets.US_ASCII);
178-
assertThat(result).isEqualTo("test.txt");
179-
180-
result = (String)ReflectionUtils.invokeMethod(encode, null, "中文.txt", StandardCharsets.UTF_8);
181-
assertThat(result).isEqualTo("UTF-8''%E4%B8%AD%E6%96%87.txt");
182-
}
183-
184190
@Test
185-
public void encodeHeaderFieldParamInvalidCharset() {
186-
Method encode = ReflectionUtils.findMethod(ContentDisposition.class,
187-
"encodeHeaderFieldParam", String.class, Charset.class);
188-
ReflectionUtils.makeAccessible(encode);
189-
assertThatIllegalArgumentException().isThrownBy(() ->
190-
ReflectionUtils.invokeMethod(encode, null, "test", StandardCharsets.UTF_16));
191-
}
192-
193-
@Test // SPR-14408
194-
public void decodeHeaderFieldParam() {
195-
Method decode = ReflectionUtils.findMethod(ContentDisposition.class,
196-
"decodeHeaderFieldParam", String.class);
197-
ReflectionUtils.makeAccessible(decode);
198-
199-
String result = (String)ReflectionUtils.invokeMethod(decode, null, "test.txt");
200-
assertThat(result).isEqualTo("test.txt");
201-
202-
result = (String)ReflectionUtils.invokeMethod(decode, null, "UTF-8''%E4%B8%AD%E6%96%87.txt");
203-
assertThat(result).isEqualTo("中文.txt");
204-
}
205-
206-
@Test
207-
public void decodeHeaderFieldParamInvalidCharset() {
208-
Method decode = ReflectionUtils.findMethod(ContentDisposition.class,
209-
"decodeHeaderFieldParam", String.class);
210-
ReflectionUtils.makeAccessible(decode);
211-
assertThatIllegalArgumentException().isThrownBy(() ->
212-
ReflectionUtils.invokeMethod(decode, null, "UTF-16''test"));
213-
}
214-
215-
@Test
216-
public void decodeHeaderFieldParamShortInvalidEncodedFilename() {
217-
Method decode = ReflectionUtils.findMethod(ContentDisposition.class,
218-
"decodeHeaderFieldParam", String.class);
219-
ReflectionUtils.makeAccessible(decode);
220-
assertThatIllegalArgumentException().isThrownBy(() ->
221-
ReflectionUtils.invokeMethod(decode, null, "UTF-8''%A"));
191+
public void formatWithEncodedFilenameUsingUsAscii() {
192+
assertThat(
193+
ContentDisposition.builder("form-data")
194+
.name("name")
195+
.filename("test.txt", StandardCharsets.US_ASCII)
196+
.build()
197+
.toString())
198+
.isEqualTo("form-data; name=\"name\"; filename=\"test.txt\"");
222199
}
223200

224201
@Test
225-
public void decodeHeaderFieldParamLongerInvalidEncodedFilename() {
226-
Method decode = ReflectionUtils.findMethod(ContentDisposition.class,
227-
"decodeHeaderFieldParam", String.class);
228-
ReflectionUtils.makeAccessible(decode);
202+
public void formatWithEncodedFilenameUsingInvalidCharset() {
229203
assertThatIllegalArgumentException().isThrownBy(() ->
230-
ReflectionUtils.invokeMethod(decode, null, "UTF-8''%A.txt"));
204+
ContentDisposition.builder("form-data")
205+
.name("name")
206+
.filename("test.txt", StandardCharsets.UTF_16)
207+
.build()
208+
.toString());
231209
}
232210

233211
}

0 commit comments

Comments
 (0)