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 e60b93b7a3..44b2641c6a 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 @@ -22,6 +22,7 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize; import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException; @@ -691,18 +692,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest String[] values = contents[i].split("=", 2); Attribute attribute; try { - String name = cleanString(values[0]); - String value = values[1]; - - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html - if (HttpHeaderValues.FILENAME.contentEquals(name)) { - // filename value is quoted string so strip them - value = value.substring(1, value.length() - 1); - } else { - // otherwise we need to clean the value - value = cleanString(value); - } - attribute = factory.createAttribute(request, name, value); + attribute = getContentDispositionAttribute(values); } catch (NullPointerException e) { throw new ErrorDataDecoderException(e); } catch (IllegalArgumentException e) { @@ -805,6 +795,38 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest } } + private static final String FILENAME_ENCODED = HttpHeaderValues.FILENAME.toString() + '*'; + + private Attribute getContentDispositionAttribute(String... values) { + String name = cleanString(values[0]); + String value = values[1]; + + // Filename can be token, quoted or encoded. See https://tools.ietf.org/html/rfc5987 + if (HttpHeaderValues.FILENAME.contentEquals(name)) { + // Value is quoted or token. Strip if quoted: + int last = value.length() - 1; + if (last > 0 && + value.charAt(0) == HttpConstants.DOUBLE_QUOTE && + value.charAt(last) == HttpConstants.DOUBLE_QUOTE) { + value = value.substring(1, last); + } + } else if (FILENAME_ENCODED.equals(name)) { + try { + name = HttpHeaderValues.FILENAME.toString(); + String[] split = value.split("'", 3); + value = QueryStringDecoder.decodeComponent(split[2], Charset.forName(split[0])); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ErrorDataDecoderException(e); + } catch (UnsupportedCharsetException e) { + throw new ErrorDataDecoderException(e); + } + } else { + // otherwise we need to clean the value + value = cleanString(value); + } + return factory.createAttribute(request, name, value); + } + /** * Get the FileUpload (new one or current one) * 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 13341074ca..31e786a38c 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 @@ -33,6 +33,7 @@ import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.CharsetUtil; import org.junit.Test; +import java.net.URLEncoder; import java.nio.charset.UnsupportedCharsetException; import java.util.Arrays; @@ -491,4 +492,138 @@ public class HttpPostRequestDecoderTest { content.release(); } } + + // https://github.com/netty/netty/pull/7265 + @Test + public void testDecodeContentDispositionFieldParameters() throws Exception { + + final String boundary = "74e78d11b0214bdcbc2f86491eeb4902"; + + String encoding = "utf-8"; + String filename = "attached_файл.txt"; + String filenameEncoded = URLEncoder.encode(filename, encoding); + + final String body = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename*=" + encoding + "''" + filenameEncoded + "\r\n" + + "\r\n" + + "foo\r\n" + + "\r\n" + + "--" + boundary + "--"; + + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, + "http://localhost", + Unpooled.wrappedBuffer(body.getBytes())); + + req.headers().add(HttpHeaderNames.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + final HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(inMemoryFactory, req); + assertFalse(decoder.getBodyHttpDatas().isEmpty()); + InterfaceHttpData part1 = decoder.getBodyHttpDatas().get(0); + assertTrue("the item should be a FileUpload", part1 instanceof FileUpload); + FileUpload fileUpload = (FileUpload) part1; + assertEquals("the filename should be decoded", filename, fileUpload.getFilename()); + decoder.destroy(); + req.release(); + } + + // https://github.com/netty/netty/pull/7265 + @Test + public void testDecodeWithLanguageContentDispositionFieldParameters() throws Exception { + + final String boundary = "74e78d11b0214bdcbc2f86491eeb4902"; + + String encoding = "utf-8"; + String filename = "attached_файл.txt"; + String language = "anything"; + String filenameEncoded = URLEncoder.encode(filename, encoding); + + final String body = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename*=" + + encoding + "'" + language + "'" + filenameEncoded + "\r\n" + + "\r\n" + + "foo\r\n" + + "\r\n" + + "--" + boundary + "--"; + + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, + "http://localhost", + Unpooled.wrappedBuffer(body.getBytes())); + + req.headers().add(HttpHeaderNames.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + final HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(inMemoryFactory, req); + assertFalse(decoder.getBodyHttpDatas().isEmpty()); + InterfaceHttpData part1 = decoder.getBodyHttpDatas().get(0); + assertTrue("the item should be a FileUpload", part1 instanceof FileUpload); + FileUpload fileUpload = (FileUpload) part1; + assertEquals("the filename should be decoded", filename, fileUpload.getFilename()); + decoder.destroy(); + req.release(); + } + + // https://github.com/netty/netty/pull/7265 + @Test + public void testDecodeMalformedNotEncodedContentDispositionFieldParameters() throws Exception { + + final String boundary = "74e78d11b0214bdcbc2f86491eeb4902"; + + final String body = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename*=not-encoded\r\n" + + "\r\n" + + "foo\r\n" + + "\r\n" + + "--" + boundary + "--"; + + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, + "http://localhost", + Unpooled.wrappedBuffer(body.getBytes())); + + req.headers().add(HttpHeaderNames.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + + try { + new HttpPostRequestDecoder(inMemoryFactory, req); + fail("Was expecting an ErrorDataDecoderException"); + } catch (HttpPostRequestDecoder.ErrorDataDecoderException e) { + assertTrue(e.getCause() instanceof ArrayIndexOutOfBoundsException); + } finally { + req.release(); + } + } + + // https://github.com/netty/netty/pull/7265 + @Test + public void testDecodeMalformedBadCharsetContentDispositionFieldParameters() throws Exception { + + final String boundary = "74e78d11b0214bdcbc2f86491eeb4902"; + + final String body = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename*=not-a-charset''filename\r\n" + + "\r\n" + + "foo\r\n" + + "\r\n" + + "--" + boundary + "--"; + + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, + "http://localhost", + Unpooled.wrappedBuffer(body.getBytes())); + + req.headers().add(HttpHeaderNames.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + + try { + new HttpPostRequestDecoder(inMemoryFactory, req); + fail("Was expecting an ErrorDataDecoderException"); + } catch (HttpPostRequestDecoder.ErrorDataDecoderException e) { + assertTrue(e.getCause() instanceof UnsupportedCharsetException); + } finally { + req.release(); + } + } }