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 04e2729ced..1fcd75981d 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 @@ -34,6 +34,7 @@ import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedInput; +import io.netty.util.internal.StringUtil; import io.netty.util.internal.ThreadLocalRandom; import java.io.File; @@ -362,12 +363,39 @@ public class HttpPostRequestEncoder implements ChunkedInput { */ public void addBodyFileUpload(String name, File file, String contentType, boolean isText) throws ErrorDataEncoderException { + addBodyFileUpload(name, file.getName(), file, contentType, isText); + } + + /** + * Add a file as a FileUpload + * + * @param name + * the name of the parameter + * @param file + * the file to be uploaded (if not Multipart mode, only the filename will be included) + * @param filename + * the filename to use for this File part, empty String will be ignored by + * the encoder + * @param contentType + * the associated contentType for the File + * @param isText + * True if this file should be transmitted in Text format (else binary) + * @throws NullPointerException + * for name and file + * @throws ErrorDataEncoderException + * if the encoding is in error or if the finalize were already done + */ + public void addBodyFileUpload(String name, String filename, File file, String contentType, boolean isText) + throws ErrorDataEncoderException { if (name == null) { throw new NullPointerException("name"); } if (file == null) { throw new NullPointerException("file"); } + if (filename == null) { + filename = StringUtil.EMPTY_STRING; + } String scontentType = contentType; String contentTransferEncoding = null; if (contentType == null) { @@ -380,7 +408,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { if (!isText) { contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value(); } - FileUpload fileUpload = factory.createFileUpload(request, name, file.getName(), scontentType, + FileUpload fileUpload = factory.createFileUpload(request, name, filename, scontentType, contentTransferEncoding, null, file.length()); try { fileUpload.setContent(file); @@ -615,12 +643,17 @@ public class HttpPostRequestEncoder implements ChunkedInput { .append(HttpHeaderNames.CONTENT_DISPOSITION) .append(": ") - .append(HttpHeaderValues.ATTACHMENT) - .append("; ") - .append(HttpHeaderValues.FILENAME) - .append("=\"") - .append(fileUpload.getFilename()) - .append("\"\r\n"); + .append(HttpHeaderValues.ATTACHMENT); + + if (!fileUpload.getFilename().isEmpty()) { + replacement.append("; ") + .append(HttpHeaderValues.FILENAME) + .append("=\"") + .append(fileUpload.getFilename()) + .append('"'); + } + + replacement.append("\r\n"); pastAttribute.setValue(replacement.toString(), 1); pastAttribute.setValue("", 2); @@ -648,16 +681,31 @@ public class HttpPostRequestEncoder implements ChunkedInput { // add mixedmultipart delimiter, mixedmultipart body header and // Data to multipart list internal.addValue("--" + multipartMixedBoundary + "\r\n"); - // Content-Disposition: attachment; filename="file1.txt" - internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.ATTACHMENT + "; " - + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + + if (fileUpload.getFilename().isEmpty()) { + // Content-Disposition: attachment + internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + + HttpHeaderValues.ATTACHMENT + "\r\n"); + } else { + // Content-Disposition: attachment; filename="file1.txt" + internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + + HttpHeaderValues.ATTACHMENT + "; " + + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + } } else { internal.addValue("--" + multipartDataBoundary + "\r\n"); - // Content-Disposition: form-data; name="files"; - // filename="file1.txt" - internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " - + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; " - + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + + if (fileUpload.getFilename().isEmpty()) { + // Content-Disposition: form-data; name="files"; + internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " + + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"\r\n"); + } else { + // Content-Disposition: form-data; name="files"; + // filename="file1.txt" + internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " + + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; " + + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + } } // Add Content-Length: xxx internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " + 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 953ee8b7d5..766d7b3503 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 @@ -74,6 +74,39 @@ public class HttpPostRequestEncoderTest { assertEquals(expected, content); } + @Test + public void testSingleFileUploadNoName() throws Exception { + DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, "http://localhost"); + + HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(request, true); + 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_LENGTH + ": 3" + "\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\"\r\n" + + CONTENT_LENGTH + ": " + file1.length() + "\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); + } + @Test public void testMultiFileUploadInMixedMode() throws Exception { DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, @@ -124,6 +157,56 @@ public class HttpPostRequestEncoderTest { assertEquals(expected, content); } + @Test + public void testMultiFileUploadInMixedModeNoName() throws Exception { + DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.POST, "http://localhost"); + + HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(request, true); + 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); + + // We have to query the value of these two fields before finalizing + // the request, which unsets one of them. + String multipartDataBoundary = encoder.multipartDataBoundary; + String multipartMixedBoundary = encoder.multipartMixedBoundary; + String content = getRequestBody(encoder); + + String expected = "--" + multipartDataBoundary + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"foo\"" + "\r\n" + + CONTENT_LENGTH + ": 3" + "\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\"" + "\r\n" + + CONTENT_TYPE + ": multipart/mixed; boundary=" + multipartMixedBoundary + "\r\n" + + "\r\n" + + "--" + multipartMixedBoundary + "\r\n" + + CONTENT_DISPOSITION + ": attachment\r\n" + + CONTENT_LENGTH + ": " + file1.length() + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + + "\r\n" + + "File 01" + StringUtil.NEWLINE + + "\r\n" + + "--" + multipartMixedBoundary + "\r\n" + + CONTENT_DISPOSITION + ": attachment\r\n" + + CONTENT_LENGTH + ": " + file2.length() + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + + "\r\n" + + "File 02" + StringUtil.NEWLINE + + "\r\n" + + "--" + multipartMixedBoundary + "--" + "\r\n" + + "--" + multipartDataBoundary + "--" + "\r\n"; + + assertEquals(expected, content); + } + @Test public void testSingleFileUploadInHtml5Mode() throws Exception { DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,