mirror of
https://github.com/apache/httpcomponents-client.git
synced 2025-02-28 21:59:09 +00:00
HTTPCLIENT-293 Refactored code in order to support multipart header field parameters in the data model and postpone the formatting and encoding of the parameters until the moment written into a stream, which is essential in order to avoid multiple encodings of the same value.
This commit is contained in:
parent
9ac5808bdb
commit
9560aef476
@ -27,9 +27,12 @@
|
|||||||
|
|
||||||
package org.apache.hc.client5.http.entity.mime;
|
package org.apache.hc.client5.http.entity.mime;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.hc.core5.http.ContentType;
|
import org.apache.hc.core5.http.ContentType;
|
||||||
|
import org.apache.hc.core5.http.NameValuePair;
|
||||||
|
import org.apache.hc.core5.http.message.BasicNameValuePair;
|
||||||
import org.apache.hc.core5.util.Args;
|
import org.apache.hc.core5.util.Args;
|
||||||
import org.apache.hc.core5.util.Asserts;
|
import org.apache.hc.core5.util.Asserts;
|
||||||
|
|
||||||
@ -72,6 +75,15 @@ public FormBodyPartBuilder setBody(final ContentBody body) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.6
|
||||||
|
*/
|
||||||
|
public FormBodyPartBuilder addField(final String name, final String value, final List<NameValuePair> parameters) {
|
||||||
|
Args.notNull(name, "Field name");
|
||||||
|
this.header.addField(new MinimalField(name, value, parameters));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public FormBodyPartBuilder addField(final String name, final String value) {
|
public FormBodyPartBuilder addField(final String name, final String value) {
|
||||||
Args.notNull(name, "Field name");
|
Args.notNull(name, "Field name");
|
||||||
this.header.addField(new MinimalField(name, value));
|
this.header.addField(new MinimalField(name, value));
|
||||||
@ -99,16 +111,12 @@ public FormBodyPart build() {
|
|||||||
headerCopy.addField(field);
|
headerCopy.addField(field);
|
||||||
}
|
}
|
||||||
if (headerCopy.getField(MIME.CONTENT_DISPOSITION) == null) {
|
if (headerCopy.getField(MIME.CONTENT_DISPOSITION) == null) {
|
||||||
final StringBuilder buffer = new StringBuilder();
|
final List<NameValuePair> fieldParameters = new ArrayList<NameValuePair>();
|
||||||
buffer.append("form-data; name=\"");
|
fieldParameters.add(new BasicNameValuePair(MIME.FIELD_PARAM_NAME, this.name));
|
||||||
buffer.append(encodeForHeader(this.name));
|
|
||||||
buffer.append("\"");
|
|
||||||
if (this.body.getFilename() != null) {
|
if (this.body.getFilename() != null) {
|
||||||
buffer.append("; filename=\"");
|
fieldParameters.add(new BasicNameValuePair(MIME.FIELD_PARAM_FILENAME, this.body.getFilename()));
|
||||||
buffer.append(encodeForHeader(this.body.getFilename()));
|
|
||||||
buffer.append("\"");
|
|
||||||
}
|
}
|
||||||
headerCopy.addField(new MinimalField(MIME.CONTENT_DISPOSITION, buffer.toString()));
|
headerCopy.addField(new MinimalField(MIME.CONTENT_DISPOSITION, "form-data", fieldParameters));
|
||||||
}
|
}
|
||||||
if (headerCopy.getField(MIME.CONTENT_TYPE) == null) {
|
if (headerCopy.getField(MIME.CONTENT_TYPE) == null) {
|
||||||
final ContentType contentType;
|
final ContentType contentType;
|
||||||
@ -136,19 +144,4 @@ public FormBodyPart build() {
|
|||||||
return new FormBodyPart(this.name, this.body, headerCopy);
|
return new FormBodyPart(this.name, this.body, headerCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String encodeForHeader(final String headerName) {
|
|
||||||
if (headerName == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
for (int i = 0; i < headerName.length(); i++) {
|
|
||||||
final char x = headerName.charAt(i);
|
|
||||||
if (x == '"' || x == '\\' || x == '\r') {
|
|
||||||
sb.append("\\");
|
|
||||||
}
|
|
||||||
sb.append(x);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ public final class MIME {
|
|||||||
public static final String CONTENT_TRANSFER_ENC = "Content-Transfer-Encoding";
|
public static final String CONTENT_TRANSFER_ENC = "Content-Transfer-Encoding";
|
||||||
public static final String CONTENT_DISPOSITION = "Content-Disposition";
|
public static final String CONTENT_DISPOSITION = "Content-Disposition";
|
||||||
|
|
||||||
|
public static final String FIELD_PARAM_NAME = "name";
|
||||||
|
public static final String FIELD_PARAM_FILENAME = "filename";
|
||||||
|
|
||||||
public static final String ENC_8BIT = "8bit";
|
public static final String ENC_8BIT = "8bit";
|
||||||
public static final String ENC_BINARY = "binary";
|
public static final String ENC_BINARY = "binary";
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@
|
|||||||
|
|
||||||
package org.apache.hc.client5.http.entity.mime;
|
package org.apache.hc.client5.http.entity.mime;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.hc.core5.http.NameValuePair;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal MIME field.
|
* Minimal MIME field.
|
||||||
*
|
*
|
||||||
@ -36,11 +42,27 @@ public class MinimalField {
|
|||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String value;
|
private final String value;
|
||||||
|
private final List<NameValuePair> parameters;
|
||||||
|
|
||||||
public MinimalField(final String name, final String value) {
|
public MinimalField(final String name, final String value) {
|
||||||
super();
|
super();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.parameters = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.6
|
||||||
|
*/
|
||||||
|
public MinimalField(final String name, final String value, final List<NameValuePair> parameters) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.parameters = parameters != null ?
|
||||||
|
Collections.unmodifiableList(new ArrayList<NameValuePair>(parameters)) : Collections.<NameValuePair>emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalField(final MinimalField from) {
|
||||||
|
this(from.name, from.value, from.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -48,7 +70,21 @@ public String getName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getBody() {
|
public String getBody() {
|
||||||
return this.value;
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(this.value);
|
||||||
|
for (int i = 0; i < this.parameters.size(); i++) {
|
||||||
|
final NameValuePair parameter = this.parameters.get(i);
|
||||||
|
sb.append("; ");
|
||||||
|
sb.append(parameter.getName());
|
||||||
|
sb.append("=\"");
|
||||||
|
sb.append(parameter.getValue());
|
||||||
|
sb.append("\"");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NameValuePair> getParameters() {
|
||||||
|
return this.parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -56,7 +92,7 @@ public String toString() {
|
|||||||
final StringBuilder buffer = new StringBuilder();
|
final StringBuilder buffer = new StringBuilder();
|
||||||
buffer.append(this.name);
|
buffer.append(this.name);
|
||||||
buffer.append(": ");
|
buffer.append(": ");
|
||||||
buffer.append(this.value);
|
buffer.append(this.getBody());
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -216,7 +217,7 @@ MultipartFormEntity buildEntity() {
|
|||||||
form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, bodyPartsCopy);
|
form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, bodyPartsCopy);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
form = new HttpStrictMultipart(charsetCopy, boundaryCopy, bodyPartsCopy);
|
form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, bodyPartsCopy);
|
||||||
}
|
}
|
||||||
return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
|
return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
package org.apache.hc.client5.http.entity.mime;
|
package org.apache.hc.client5.http.entity.mime;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -57,27 +56,6 @@ public void testBuildBodyPartBasics() throws Exception {
|
|||||||
header.getFields());
|
header.getFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCharacterStuffing() throws Exception {
|
|
||||||
final FormBodyPartBuilder builder = FormBodyPartBuilder.create();
|
|
||||||
final InputStreamBody fileBody = new InputStreamBody(new ByteArrayInputStream("hello world".getBytes("UTF-8")), "stuff_with \"quotes\" and \\slashes\\.bin");
|
|
||||||
final FormBodyPart bodyPart2 = builder
|
|
||||||
.setName("yada_with \"quotes\" and \\slashes\\")
|
|
||||||
.setBody(fileBody)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Assert.assertNotNull(bodyPart2);
|
|
||||||
Assert.assertEquals("yada_with \"quotes\" and \\slashes\\", bodyPart2.getName());
|
|
||||||
Assert.assertEquals(fileBody, bodyPart2.getBody());
|
|
||||||
final Header header2 = bodyPart2.getHeader();
|
|
||||||
Assert.assertNotNull(header2);
|
|
||||||
assertFields(Arrays.asList(
|
|
||||||
new MinimalField("Content-Disposition", "form-data; name=\"yada_with \\\"quotes\\\" and \\\\slashes\\\\\"; filename=\"stuff_with \\\"quotes\\\" and \\\\slashes\\\\.bin\""),
|
|
||||||
new MinimalField("Content-Type", "application/octet-stream"),
|
|
||||||
new MinimalField("Content-Transfer-Encoding", "binary")),
|
|
||||||
header2.getFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildBodyPartMultipleBuilds() throws Exception {
|
public void testBuildBodyPartMultipleBuilds() throws Exception {
|
||||||
final StringBody stringBody = new StringBody("stuff", ContentType.TEXT_PLAIN);
|
final StringBody stringBody = new StringBody("stuff", ContentType.TEXT_PLAIN);
|
||||||
|
@ -28,11 +28,14 @@
|
|||||||
package org.apache.hc.client5.http.entity.mime;
|
package org.apache.hc.client5.http.entity.mime;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.hc.core5.http.ContentType;
|
import org.apache.hc.core5.http.ContentType;
|
||||||
|
import org.apache.hc.core5.http.NameValuePair;
|
||||||
import org.apache.hc.core5.http.message.BasicNameValuePair;
|
import org.apache.hc.core5.http.message.BasicNameValuePair;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -115,4 +118,32 @@ public void testMultipartCustomContentTypeParameterOverrides() throws Exception
|
|||||||
entity.getContentType());
|
entity.getContentType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipartWriteTo() throws Exception {
|
||||||
|
final List<NameValuePair> parameters = new ArrayList<>();
|
||||||
|
parameters.add(new BasicNameValuePair(MIME.FIELD_PARAM_NAME, "test"));
|
||||||
|
parameters.add(new BasicNameValuePair(MIME.FIELD_PARAM_FILENAME, "hello world"));
|
||||||
|
final MultipartFormEntity entity = MultipartEntityBuilder.create()
|
||||||
|
.setStrictMode()
|
||||||
|
.setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
|
||||||
|
.addPart(new FormBodyPartBuilder()
|
||||||
|
.setName("test")
|
||||||
|
.setBody(new StringBody("hello world", ContentType.TEXT_PLAIN))
|
||||||
|
.addField("Content-Disposition", "multipart/form-data", parameters)
|
||||||
|
.build())
|
||||||
|
.buildEntity();
|
||||||
|
|
||||||
|
|
||||||
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
entity.getMultipart().writeTo(out);
|
||||||
|
out.close();
|
||||||
|
Assert.assertEquals("--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
|
||||||
|
"Content-Disposition: multipart/form-data; name=\"test\"; filename=\"hello world\"\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=ISO-8859-1\r\n" +
|
||||||
|
"Content-Transfer-Encoding: 8bit\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"hello world\r\n" +
|
||||||
|
"--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n", out.toString(StandardCharsets.US_ASCII.name()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user