Skip to content

Commit 6ecc67f

Browse files
fredericBregiernormanmaurer
authored andcommitted
Accept ';' '\\"' in the filename of HTTP Content-Disposition header
Motivation: HttpPostMultipartRequestDecoder threw an ArrayIndexOutOfBoundsException when trying to decode Content-Disposition header with filename containing ';' or protected \\". See issue netty#3326 and netty#3327. Modifications: Added splitMultipartHeaderValues method which cares about quotes, and use it in splitMultipartHeader method, instead of StringUtils.split. Result: Filenames can contain semicolons and protected \\".
1 parent fb0c788 commit 6ecc67f

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1784,7 +1784,7 @@ private static String[] splitMultipartHeader(String sb) {
17841784
String svalue = sb.substring(valueStart, valueEnd);
17851785
String[] values;
17861786
if (svalue.indexOf(';') >= 0) {
1787-
values = StringUtil.split(svalue, ';');
1787+
values = splitMultipartHeaderValues(svalue);
17881788
} else {
17891789
values = StringUtil.split(svalue, ',');
17901790
}
@@ -1797,4 +1797,38 @@ private static String[] splitMultipartHeader(String sb) {
17971797
}
17981798
return array;
17991799
}
1800+
1801+
/**
1802+
* Split one header value in Multipart
1803+
* @return an array of String where values that were separated by ';' or ','
1804+
*/
1805+
private static String[] splitMultipartHeaderValues(String svalue) {
1806+
List<String> values = new ArrayList<String>(1);
1807+
boolean inQuote = false;
1808+
boolean escapeNext = false;
1809+
int start = 0;
1810+
for (int i = 0; i < svalue.length(); i++) {
1811+
char c = svalue.charAt(i);
1812+
if (inQuote) {
1813+
if (escapeNext) {
1814+
escapeNext = false;
1815+
} else {
1816+
if (c == '\\') {
1817+
escapeNext = true;
1818+
} else if (c == '"') {
1819+
inQuote = false;
1820+
}
1821+
}
1822+
} else {
1823+
if (c == '"') {
1824+
inQuote = true;
1825+
} else if (c == ';') {
1826+
values.add(svalue.substring(start, i));
1827+
start = i + 1;
1828+
}
1829+
}
1830+
}
1831+
values.add(svalue.substring(start));
1832+
return values.toArray(new String[values.size()]);
1833+
}
18001834
}

codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,30 @@ public void testChunkCorrect() throws Exception {
321321
decoder.offer(part3);
322322
decoder.offer(part4);
323323
}
324+
325+
// See https://github.com/netty/netty/issues/3326
326+
@Test
327+
public void testFilenameContainingSemicolon() throws Exception {
328+
final String boundary = "dLV9Wyq26L_-JQxk6ferf-RT153LhOO";
329+
final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
330+
"http://localhost");
331+
req.headers().add(HttpHeaders.Names.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary);
332+
// Force to use memory-based data.
333+
final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false);
334+
final String data = "asdf";
335+
final String filename = "tmp;0.txt";
336+
final String body =
337+
"--" + boundary + "\r\n" +
338+
"Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n" +
339+
"Content-Type: image/gif\r\n" +
340+
"\r\n" +
341+
data + "\r\n" +
342+
"--" + boundary + "--\r\n";
343+
344+
req.content().writeBytes(body.getBytes(CharsetUtil.UTF_8.name()));
345+
// Create decoder instance to test.
346+
final HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(inMemoryFactory, req);
347+
assertFalse(decoder.getBodyHttpDatas().isEmpty());
348+
decoder.destroy();
349+
}
324350
}

0 commit comments

Comments
 (0)