fix #6066 Support optional filename in HttpPostRequestEncoder

Motivation:

According to https://www.ietf.org/rfc/rfc2388.txt 4.4, filename after "content-disposition" is optional and arbitrary (does not need to match a real filename).

Modifications:

This change supports an extra addBodyFileUpload overload to precise the filename (default to File.getName). If empty or null this argument should be ignored during encoding.

Result:
- A backward-compatible addBodyFileUpload(String, File, String, boolean) to use file.getName() as filename.
- A new addBodyFileUpload(String, String, File, String, boolean) overload to precise filename
- Couple of tests for the empty use case
This commit is contained in:
Stephane Maldini 2016-11-28 12:49:36 +00:00 committed by Norman Maurer
parent 55c291ae5b
commit ea0ddc0ea2
2 changed files with 146 additions and 15 deletions

View File

@ -34,6 +34,7 @@ import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.stream.ChunkedInput; import io.netty.handler.stream.ChunkedInput;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThreadLocalRandom; import io.netty.util.internal.ThreadLocalRandom;
import java.io.File; import java.io.File;
@ -362,12 +363,39 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
*/ */
public void addBodyFileUpload(String name, File file, String contentType, boolean isText) public void addBodyFileUpload(String name, File file, String contentType, boolean isText)
throws ErrorDataEncoderException { 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) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
if (file == null) { if (file == null) {
throw new NullPointerException("file"); throw new NullPointerException("file");
} }
if (filename == null) {
filename = StringUtil.EMPTY_STRING;
}
String scontentType = contentType; String scontentType = contentType;
String contentTransferEncoding = null; String contentTransferEncoding = null;
if (contentType == null) { if (contentType == null) {
@ -380,7 +408,7 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
if (!isText) { if (!isText) {
contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value(); 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()); contentTransferEncoding, null, file.length());
try { try {
fileUpload.setContent(file); fileUpload.setContent(file);
@ -615,12 +643,17 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
.append(HttpHeaderNames.CONTENT_DISPOSITION) .append(HttpHeaderNames.CONTENT_DISPOSITION)
.append(": ") .append(": ")
.append(HttpHeaderValues.ATTACHMENT) .append(HttpHeaderValues.ATTACHMENT);
.append("; ")
if (!fileUpload.getFilename().isEmpty()) {
replacement.append("; ")
.append(HttpHeaderValues.FILENAME) .append(HttpHeaderValues.FILENAME)
.append("=\"") .append("=\"")
.append(fileUpload.getFilename()) .append(fileUpload.getFilename())
.append("\"\r\n"); .append('"');
}
replacement.append("\r\n");
pastAttribute.setValue(replacement.toString(), 1); pastAttribute.setValue(replacement.toString(), 1);
pastAttribute.setValue("", 2); pastAttribute.setValue("", 2);
@ -648,17 +681,32 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
// add mixedmultipart delimiter, mixedmultipart body header and // add mixedmultipart delimiter, mixedmultipart body header and
// Data to multipart list // Data to multipart list
internal.addValue("--" + multipartMixedBoundary + "\r\n"); internal.addValue("--" + multipartMixedBoundary + "\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" // Content-Disposition: attachment; filename="file1.txt"
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.ATTACHMENT + "; " internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
+ HttpHeaderValues.ATTACHMENT + "; "
+ HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
}
} else { } else {
internal.addValue("--" + multipartDataBoundary + "\r\n"); internal.addValue("--" + multipartDataBoundary + "\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"; // Content-Disposition: form-data; name="files";
// filename="file1.txt" // filename="file1.txt"
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
+ HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; " + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; "
+ HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
} }
}
// Add Content-Length: xxx // Add Content-Length: xxx
internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " + internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
fileUpload.length() + "\r\n"); fileUpload.length() + "\r\n");

View File

@ -74,6 +74,39 @@ public class HttpPostRequestEncoderTest {
assertEquals(expected, content); 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 @Test
public void testMultiFileUploadInMixedMode() throws Exception { public void testMultiFileUploadInMixedMode() throws Exception {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
@ -124,6 +157,56 @@ public class HttpPostRequestEncoderTest {
assertEquals(expected, content); 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 @Test
public void testSingleFileUploadInHtml5Mode() throws Exception { public void testSingleFileUploadInHtml5Mode() throws Exception {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,