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:
parent
55c291ae5b
commit
ea0ddc0ea2
@ -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("; ")
|
|
||||||
.append(HttpHeaderValues.FILENAME)
|
if (!fileUpload.getFilename().isEmpty()) {
|
||||||
.append("=\"")
|
replacement.append("; ")
|
||||||
.append(fileUpload.getFilename())
|
.append(HttpHeaderValues.FILENAME)
|
||||||
.append("\"\r\n");
|
.append("=\"")
|
||||||
|
.append(fileUpload.getFilename())
|
||||||
|
.append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement.append("\r\n");
|
||||||
|
|
||||||
pastAttribute.setValue(replacement.toString(), 1);
|
pastAttribute.setValue(replacement.toString(), 1);
|
||||||
pastAttribute.setValue("", 2);
|
pastAttribute.setValue("", 2);
|
||||||
@ -648,16 +681,31 @@ 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");
|
||||||
// Content-Disposition: attachment; filename="file1.txt"
|
|
||||||
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.ATTACHMENT + "; "
|
if (fileUpload.getFilename().isEmpty()) {
|
||||||
+ HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
|
// 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 {
|
} else {
|
||||||
internal.addValue("--" + multipartDataBoundary + "\r\n");
|
internal.addValue("--" + multipartDataBoundary + "\r\n");
|
||||||
// Content-Disposition: form-data; name="files";
|
|
||||||
// filename="file1.txt"
|
if (fileUpload.getFilename().isEmpty()) {
|
||||||
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
|
// Content-Disposition: form-data; name="files";
|
||||||
+ HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; "
|
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
|
||||||
+ HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
|
+ 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
|
// Add Content-Length: xxx
|
||||||
internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
|
internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user