diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java index 9b66eedd96..d25b09bcbf 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -55,15 +55,26 @@ public class HttpPostRequestEncoder implements ChunkedInput { */ public enum EncoderMode { /** - * Legacy mode which should work for most. It is known to not work with OAUTH. For OAUTH use - * {@link EncoderMode#RFC3986}. The W3C form recommentations this for submitting post form data. + * Legacy mode which should work for most. It is known to not work with OAUTH. For OAUTH use + * {@link EncoderMode#RFC3986}. The W3C form recommentations this for submitting post form data. */ RFC1738, /** * Mode which is more new and is used for OAUTH */ - RFC3986 + RFC3986, + + /** + * The HTML5 spec disallows mixed mode in multipart/form-data + * requests. More concretely this means that more files submitted + * under the same name will not be encoded using mixed mode, but + * will be treated as distinct fields. + * + * Reference: + * http://www.w3.org/TR/html5/forms.html#multipart-form-data + */ + HTML5 } private static final Map percentEncodings = new HashMap(); @@ -372,7 +383,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { } /** - * Add a series of Files associated with one File parameter (implied Mixed mode in Multipart) + * Add a series of Files associated with one File parameter * * @param name * the name of the parameter @@ -534,7 +545,8 @@ public class HttpPostRequestEncoder implements ChunkedInput { duringMixedMode = false; } } else { - if (currentFileUpload != null && currentFileUpload.getName().equals(fileUpload.getName())) { + if (encoderMode != EncoderMode.HTML5 && currentFileUpload != null + && currentFileUpload.getName().equals(fileUpload.getName())) { // create a new mixed mode (from previous file) // change multipart body header of previous file into @@ -553,7 +565,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { // * Content-Type: multipart/mixed; boundary=BbC04y // * // * --BbC04y - // * Content-Disposition: file; filename="file1.txt" + // * Content-Disposition: attachment; filename="file1.txt" // Content-Type: text/plain initMixedMultipart(); InternalAttribute pastAttribute = (InternalAttribute) multipartHttpDatas.get(multipartHttpDatas @@ -625,7 +637,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { // add mixedmultipart delimiter, mixedmultipart body header and // Data to multipart list internal.addValue("--" + multipartMixedBoundary + "\r\n"); - // Content-Disposition: file; filename="file1.txt" + // Content-Disposition: attachment; filename="file1.txt" internal.addValue(HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.ATTACHMENT + "; " + HttpPostBodyUtil.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); } else { @@ -700,7 +712,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { // "multipart/form-data; boundary=--89421926422648" String lowercased = contentType.toLowerCase(); if (lowercased.startsWith(HttpHeaders.Values.MULTIPART_FORM_DATA) || - lowercased.startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED)) { + lowercased.startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED)) { // ignore } else { headers.add(HttpHeaders.Names.CONTENT_TYPE, contentType); @@ -729,7 +741,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { if (transferEncoding != null) { headers.remove(HttpHeaders.Names.TRANSFER_ENCODING); for (String v : transferEncoding) { - if (v.equalsIgnoreCase(HttpHeaders.Values.CHUNKED)) { + if (HttpHeaders.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) { // ignore } else { headers.add(HttpHeaders.Names.TRANSFER_ENCODING, v); @@ -1149,6 +1161,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { private static final class WrappedFullHttpRequest extends WrappedHttpRequest implements FullHttpRequest { private final HttpContent content; + private WrappedFullHttpRequest(HttpRequest request, HttpContent content) { super(request); this.content = content; diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java index a972454824..26273e6160 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java @@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder.EncoderMode; import io.netty.util.CharsetUtil; import io.netty.util.internal.StringUtil; import org.junit.Test; @@ -110,6 +111,81 @@ public class HttpPostRequestEncoderTest { assertEquals(expected, content); } + @Test + public void testSingleFileUploadInHtml5Mode() throws Exception { + DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, "http://localhost"); + + DefaultHttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); + + HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(factory, + request, true, CharsetUtil.UTF_8, EncoderMode.HTML5); + File file1 = new File(getClass().getResource("/file-01.txt").toURI()); + File file2 = new File(getClass().getResource("/file-02.txt").toURI()); + encoder.addBodyAttribute("foo", "bar"); + encoder.addBodyFileUpload("quux", file1, "text/plain", false); + encoder.addBodyFileUpload("quux", file2, "text/plain", false); + + String multipartDataBoundary = encoder.multipartDataBoundary; + String content = getRequestBody(encoder); + + String expected = "--" + multipartDataBoundary + "\r\n" + + "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + + "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + "\r\n" + + "bar" + "\r\n" + + "--" + multipartDataBoundary + "\r\n" + + "Content-Disposition: form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + + "Content-Type: text/plain" + "\r\n" + + "Content-Transfer-Encoding: binary" + "\r\n" + + "\r\n" + + "File 01" + StringUtil.NEWLINE + "\r\n" + + "--" + multipartDataBoundary + "\r\n" + + "Content-Disposition: form-data; name=\"quux\"; filename=\"file-02.txt\"" + "\r\n" + + "Content-Type: text/plain" + "\r\n" + + "Content-Transfer-Encoding: binary" + "\r\n" + + "\r\n" + + "File 02" + StringUtil.NEWLINE + + "\r\n" + + "--" + multipartDataBoundary + "--" + "\r\n"; + + assertEquals(expected, content); + } + + @Test + public void testMultiFileUploadInHtml5Mode() throws Exception { + DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, "http://localhost"); + + DefaultHttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); + + HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(factory, + request, true, CharsetUtil.UTF_8, EncoderMode.HTML5); + File file1 = new File(getClass().getResource("/file-01.txt").toURI()); + encoder.addBodyAttribute("foo", "bar"); + encoder.addBodyFileUpload("quux", file1, "text/plain", false); + + String multipartDataBoundary = encoder.multipartDataBoundary; + String content = getRequestBody(encoder); + + String expected = "--" + multipartDataBoundary + "\r\n" + + "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + + "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + "\r\n" + + "bar" + + "\r\n" + + "--" + multipartDataBoundary + "\r\n" + + "Content-Disposition: form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + + "Content-Type: text/plain" + "\r\n" + + "Content-Transfer-Encoding: binary" + "\r\n" + + "\r\n" + + "File 01" + StringUtil.NEWLINE + + "\r\n" + + "--" + multipartDataBoundary + "--" + "\r\n"; + + assertEquals(expected, content); + } + private static String getRequestBody(HttpPostRequestEncoder encoder) throws Exception { encoder.finalizeRequest();