diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java index 3d22694d13..f49a14364d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java @@ -1786,7 +1786,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest String svalue = sb.substring(valueStart, valueEnd); String[] values; if (svalue.indexOf(';') >= 0) { - values = StringUtil.split(svalue, ';'); + values = splitMultipartHeaderValues(svalue); } else { values = StringUtil.split(svalue, ','); } @@ -1799,4 +1799,38 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest } return array; } + + /** + * Split one header value in Multipart + * @return an array of String where values that were separated by ';' or ',' + */ + private static String[] splitMultipartHeaderValues(String svalue) { + List values = new ArrayList(1); + boolean inQuote = false; + boolean escapeNext = false; + int start = 0; + for (int i = 0; i < svalue.length(); i++) { + char c = svalue.charAt(i); + if (inQuote) { + if (escapeNext) { + escapeNext = false; + } else { + if (c == '\\') { + escapeNext = true; + } else if (c == '"') { + inQuote = false; + } + } + } else { + if (c == '"') { + inQuote = true; + } else if (c == ';') { + values.add(svalue.substring(start, i)); + start = i + 1; + } + } + } + values.add(svalue.substring(start)); + return values.toArray(new String[values.size()]); + } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java index ae54ffb34f..6c0294d69f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java @@ -321,4 +321,30 @@ public class HttpPostRequestDecoderTest { decoder.offer(part3); decoder.offer(part4); } + + // See https://github.com/netty/netty/issues/3326 + @Test + public void testFilenameContainingSemicolon() throws Exception { + final String boundary = "dLV9Wyq26L_-JQxk6ferf-RT153LhOO"; + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, + "http://localhost"); + req.headers().add(HttpHeaders.Names.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + // Force to use memory-based data. + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + final String data = "asdf"; + final String filename = "tmp;0.txt"; + final String body = + "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n" + + "Content-Type: image/gif\r\n" + + "\r\n" + + data + "\r\n" + + "--" + boundary + "--\r\n"; + + req.content().writeBytes(body.getBytes(CharsetUtil.UTF_8.name())); + // Create decoder instance to test. + final HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(inMemoryFactory, req); + assertFalse(decoder.getBodyHttpDatas().isEmpty()); + decoder.destroy(); + } }