Refactor to enable support for non-form based multipart requests

This commit is contained in:
Adam Retter 2019-06-02 15:47:30 +01:00 committed by Oleg Kalnichevski
parent 91f9278b9a
commit d71d0f5a4c
15 changed files with 757 additions and 75 deletions

View File

@ -46,7 +46,7 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
* *
* @since 4.3 * @since 4.3
*/ */
abstract class AbstractMultipartForm { abstract class AbstractMultipartFormat {
static ByteArrayBuffer encode( static ByteArrayBuffer encode(
final Charset charset, final String string) { final Charset charset, final String string) {
@ -103,25 +103,25 @@ abstract class AbstractMultipartForm {
* @param boundary to use - must not be {@code null} * @param boundary to use - must not be {@code null}
* @throws IllegalArgumentException if charset is null or boundary is null * @throws IllegalArgumentException if charset is null or boundary is null
*/ */
public AbstractMultipartForm(final Charset charset, final String boundary) { public AbstractMultipartFormat(final Charset charset, final String boundary) {
super(); super();
Args.notNull(boundary, "Multipart boundary"); Args.notNull(boundary, "Multipart boundary");
this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1; this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1;
this.boundary = boundary; this.boundary = boundary;
} }
public AbstractMultipartForm(final String boundary) { public AbstractMultipartFormat(final String boundary) {
this(null, boundary); this(null, boundary);
} }
public abstract List<FormBodyPart> getBodyParts(); public abstract List<MultipartPart> getParts();
void doWriteTo( void doWriteTo(
final OutputStream out, final OutputStream out,
final boolean writeContent) throws IOException { final boolean writeContent) throws IOException {
final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary); final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
for (final FormBodyPart part: getBodyParts()) { for (final MultipartPart part: getParts()) {
writeBytes(TWO_DASHES, out); writeBytes(TWO_DASHES, out);
writeBytes(boundaryEncoded, out); writeBytes(boundaryEncoded, out);
writeBytes(CR_LF, out); writeBytes(CR_LF, out);
@ -145,7 +145,7 @@ abstract class AbstractMultipartForm {
* Write the multipart header fields; depends on the style. * Write the multipart header fields; depends on the style.
*/ */
protected abstract void formatMultipartHeader( protected abstract void formatMultipartHeader(
final FormBodyPart part, final MultipartPart part,
final OutputStream out) throws IOException; final OutputStream out) throws IOException;
/** /**
@ -173,7 +173,7 @@ abstract class AbstractMultipartForm {
*/ */
public long getTotalLength() { public long getTotalLength() {
long contentLen = 0; long contentLen = 0;
for (final FormBodyPart part: getBodyParts()) { for (final MultipartPart part: getParts()) {
final ContentBody body = part.getBody(); final ContentBody body = part.getBody();
final long len = body.getContentLength(); final long len = body.getContentLength();
if (len >= 0) { if (len >= 0) {

View File

@ -36,36 +36,24 @@ import org.apache.hc.core5.util.Args;
* *
* @since 4.0 * @since 4.0
*/ */
public class FormBodyPart { public class FormBodyPart extends MultipartPart {
private final String name; private final String name;
private final Header header;
private final ContentBody body;
FormBodyPart(final String name, final ContentBody body, final Header header) { FormBodyPart(final String name, final ContentBody body, final Header header) {
super(); super(body, header);
Args.notNull(name, "Name"); Args.notNull(name, "Name");
Args.notNull(body, "Body"); Args.notNull(body, "Body");
this.name = name; this.name = name;
this.body = body;
this.header = header != null ? header : new Header();
} }
public String getName() { public String getName() {
return this.name; return this.name;
} }
public ContentBody getBody() {
return this.body;
}
public Header getHeader() {
return this.header;
}
public void addField(final String name, final String value) { public void addField(final String name, final String value) {
Args.notNull(name, "Field name"); Args.notNull(name, "Field name");
this.header.addField(new MinimalField(name, value)); super.addField(name, value);
} }
} }

View File

@ -38,20 +38,20 @@ import java.util.List;
* *
* @since 4.3 * @since 4.3
*/ */
class HttpBrowserCompatibleMultipart extends AbstractMultipartForm { class HttpBrowserCompatibleMultipart extends AbstractMultipartFormat {
private final List<FormBodyPart> parts; private final List<MultipartPart> parts;
public HttpBrowserCompatibleMultipart( public HttpBrowserCompatibleMultipart(
final Charset charset, final Charset charset,
final String boundary, final String boundary,
final List<FormBodyPart> parts) { final List<MultipartPart> parts) {
super(charset, boundary); super(charset, boundary);
this.parts = parts; this.parts = parts;
} }
@Override @Override
public List<FormBodyPart> getBodyParts() { public List<MultipartPart> getParts() {
return this.parts; return this.parts;
} }
@ -60,13 +60,15 @@ class HttpBrowserCompatibleMultipart extends AbstractMultipartForm {
*/ */
@Override @Override
protected void formatMultipartHeader( protected void formatMultipartHeader(
final FormBodyPart part, final MultipartPart part,
final OutputStream out) throws IOException { final OutputStream out) throws IOException {
// For browser-compatible, only write Content-Disposition // For browser-compatible, only write Content-Disposition
// Use content charset // Use content charset
final Header header = part.getHeader(); final Header header = part.getHeader();
final MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION); final MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION);
if (cd != null) {
writeField(cd, this.charset, out); writeField(cd, this.charset, out);
}
final String filename = part.getBody().getFilename(); final String filename = part.getBody().getFilename();
if (filename != null) { if (filename != null) {
final MinimalField ct = header.getField(MIME.CONTENT_TYPE); final MinimalField ct = header.getField(MIME.CONTENT_TYPE);

View File

@ -40,26 +40,26 @@ import java.util.List;
* *
* @since 4.3 * @since 4.3
*/ */
class HttpRFC6532Multipart extends AbstractMultipartForm { class HttpRFC6532Multipart extends AbstractMultipartFormat {
private final List<FormBodyPart> parts; private final List<MultipartPart> parts;
public HttpRFC6532Multipart( public HttpRFC6532Multipart(
final Charset charset, final Charset charset,
final String boundary, final String boundary,
final List<FormBodyPart> parts) { final List<MultipartPart> parts) {
super(charset, boundary); super(charset, boundary);
this.parts = parts; this.parts = parts;
} }
@Override @Override
public List<FormBodyPart> getBodyParts() { public List<MultipartPart> getParts() {
return this.parts; return this.parts;
} }
@Override @Override
protected void formatMultipartHeader( protected void formatMultipartHeader(
final FormBodyPart part, final MultipartPart part,
final OutputStream out) throws IOException { final OutputStream out) throws IOException {
// For RFC6532, we output all fields with UTF-8 encoding. // For RFC6532, we output all fields with UTF-8 encoding.

View File

@ -40,27 +40,27 @@ import org.apache.commons.codec.DecoderException;
import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.ByteArrayBuffer;
public class HttpRFC7578Multipart extends AbstractMultipartForm { public class HttpRFC7578Multipart extends AbstractMultipartFormat {
private static final PercentCodec PERCENT_CODEC = new PercentCodec(); private static final PercentCodec PERCENT_CODEC = new PercentCodec();
private final List<FormBodyPart> parts; private final List<MultipartPart> parts;
public HttpRFC7578Multipart( public HttpRFC7578Multipart(
final Charset charset, final Charset charset,
final String boundary, final String boundary,
final List<FormBodyPart> parts) { final List<MultipartPart> parts) {
super(charset, boundary); super(charset, boundary);
this.parts = parts; this.parts = parts;
} }
@Override @Override
public List<FormBodyPart> getBodyParts() { public List<MultipartPart> getParts() {
return parts; return parts;
} }
@Override @Override
protected void formatMultipartHeader(final FormBodyPart part, final OutputStream out) throws IOException { protected void formatMultipartHeader(final MultipartPart part, final OutputStream out) throws IOException {
for (final MinimalField field: part.getHeader()) { for (final MinimalField field: part.getHeader()) {
if (MIME.CONTENT_DISPOSITION.equalsIgnoreCase(field.getName())) { if (MIME.CONTENT_DISPOSITION.equalsIgnoreCase(field.getName())) {
writeBytes(field.getName(), charset, out); writeBytes(field.getName(), charset, out);

View File

@ -39,26 +39,26 @@ import java.util.List;
* *
* @since 4.3 * @since 4.3
*/ */
class HttpStrictMultipart extends AbstractMultipartForm { class HttpStrictMultipart extends AbstractMultipartFormat {
private final List<FormBodyPart> parts; private final List<MultipartPart> parts;
public HttpStrictMultipart( public HttpStrictMultipart(
final Charset charset, final Charset charset,
final String boundary, final String boundary,
final List<FormBodyPart> parts) { final List<MultipartPart> parts) {
super(charset, boundary); super(charset, boundary);
this.parts = parts; this.parts = parts;
} }
@Override @Override
public List<FormBodyPart> getBodyParts() { public List<MultipartPart> getParts() {
return this.parts; return this.parts;
} }
@Override @Override
protected void formatMultipartHeader( protected void formatMultipartHeader(
final FormBodyPart part, final MultipartPart part,
final OutputStream out) throws IOException { final OutputStream out) throws IOException {
// For strict, we output all fields with MIME-standard encoding. // For strict, we output all fields with MIME-standard encoding.

View File

@ -45,7 +45,7 @@ import org.apache.hc.core5.util.Args;
/** /**
* Builder for multipart {@link HttpEntity}s. * Builder for multipart {@link HttpEntity}s.
* *
* @since 4.3 * @since 5.0
*/ */
public class MultipartEntityBuilder { public class MultipartEntityBuilder {
@ -56,13 +56,14 @@ public class MultipartEntityBuilder {
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray(); .toCharArray();
private final static String DEFAULT_SUBTYPE = "form-data"; private final static String FORM_SUBTYPE = "form-data";
private final static String MIXED_SUBTYPE = "mixed";
private ContentType contentType; private ContentType contentType;
private HttpMultipartMode mode = HttpMultipartMode.STRICT; private HttpMultipartMode mode = HttpMultipartMode.STRICT;
private String boundary = null; private String boundary = null;
private Charset charset = null; private Charset charset = null;
private List<FormBodyPart> bodyParts = null; private List<MultipartPart> multipartParts = null;
public static MultipartEntityBuilder create() { public static MultipartEntityBuilder create() {
return new MultipartEntityBuilder(); return new MultipartEntityBuilder();
@ -117,14 +118,14 @@ public class MultipartEntityBuilder {
/** /**
* @since 4.4 * @since 4.4
*/ */
public MultipartEntityBuilder addPart(final FormBodyPart bodyPart) { public MultipartEntityBuilder addPart(final MultipartPart multipartPart) {
if (bodyPart == null) { if (multipartPart == null) {
return this; return this;
} }
if (this.bodyParts == null) { if (this.multipartParts == null) {
this.bodyParts = new ArrayList<>(); this.multipartParts = new ArrayList<>();
} }
this.bodyParts.add(bodyPart); this.multipartParts.add(multipartPart);
return this; return this;
} }
@ -202,28 +203,46 @@ public class MultipartEntityBuilder {
paramsList.add(new BasicNameValuePair("charset", charsetCopy.name())); paramsList.add(new BasicNameValuePair("charset", charsetCopy.name()));
} }
final NameValuePair[] params = paramsList.toArray(new NameValuePair[paramsList.size()]); final NameValuePair[] params = paramsList.toArray(new NameValuePair[paramsList.size()]);
final ContentType contentTypeCopy = contentType != null ?
contentType.withParameters(params) : final ContentType contentTypeCopy;
ContentType.create("multipart/" + DEFAULT_SUBTYPE, params); if (contentType != null) {
final List<FormBodyPart> bodyPartsCopy = bodyParts != null ? new ArrayList<>(bodyParts) : contentTypeCopy = contentType.withParameters(params);
Collections.<FormBodyPart>emptyList(); } else {
boolean formData = false;
if (multipartParts != null) {
for (final MultipartPart multipartPart : multipartParts) {
if (multipartPart instanceof FormBodyPart) {
formData = true;
break;
}
}
}
if (formData) {
contentTypeCopy = ContentType.create("multipart/" + FORM_SUBTYPE, params);
} else {
contentTypeCopy = ContentType.create("multipart/" + MIXED_SUBTYPE, params);
}
}
final List<MultipartPart> multipartPartsCopy = multipartParts != null ? new ArrayList<>(multipartParts) :
Collections.<MultipartPart>emptyList();
final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT; final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT;
final AbstractMultipartForm form; final AbstractMultipartFormat form;
switch (modeCopy) { switch (modeCopy) {
case BROWSER_COMPATIBLE: case BROWSER_COMPATIBLE:
form = new HttpBrowserCompatibleMultipart(charsetCopy, boundaryCopy, bodyPartsCopy); form = new HttpBrowserCompatibleMultipart(charsetCopy, boundaryCopy, multipartPartsCopy);
break; break;
case RFC6532: case RFC6532:
form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, bodyPartsCopy); form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
break; break;
case RFC7578: case RFC7578:
if (charsetCopy == null) { if (charsetCopy == null) {
charsetCopy = StandardCharsets.UTF_8; charsetCopy = StandardCharsets.UTF_8;
} }
form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, bodyPartsCopy); form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
break; break;
default: default:
form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, bodyPartsCopy); form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, multipartPartsCopy);
} }
return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength()); return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
} }

View File

@ -43,12 +43,12 @@ import org.apache.hc.core5.http.HttpEntity;
class MultipartFormEntity implements HttpEntity { class MultipartFormEntity implements HttpEntity {
private final AbstractMultipartForm multipart; private final AbstractMultipartFormat multipart;
private final ContentType contentType; private final ContentType contentType;
private final long contentLength; private final long contentLength;
MultipartFormEntity( MultipartFormEntity(
final AbstractMultipartForm multipart, final AbstractMultipartFormat multipart,
final ContentType contentType, final ContentType contentType,
final long contentLength) { final long contentLength) {
super(); super();
@ -57,7 +57,7 @@ class MultipartFormEntity implements HttpEntity {
this.contentLength = contentLength; this.contentLength = contentLength;
} }
AbstractMultipartForm getMultipart() { AbstractMultipartFormat getMultipart() {
return this.multipart; return this.multipart;
} }

View File

@ -0,0 +1,63 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.entity.mime;
/**
* MultipartPart class represents a content body that can be used as a part of multipart encoded
* entities. This class automatically populates the header with standard fields based on
* the content description of the enclosed body.
*
* @since 5.0
*/
public class MultipartPart {
private final Header header;
private final ContentBody body;
MultipartPart(final ContentBody body, final Header header) {
super();
this.body = body;
this.header = header != null ? header : new Header();
}
public ContentBody getBody() {
return this.body;
}
public Header getHeader() {
return this.header;
}
void addField(final String name, final String value) {
addField(new MinimalField(name, value));
}
void addField(final MinimalField field) {
this.header.addField(field);
}
}

View File

@ -0,0 +1,121 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.entity.mime;
import java.util.List;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts;
/**
* Builder for individual {@link MultipartPart}s.
*
* @since 4.4
*/
public class MultipartPartBuilder {
private ContentBody body;
private final Header header;
public static MultipartPartBuilder create(final ContentBody body) {
return new MultipartPartBuilder(body);
}
public static MultipartPartBuilder create() {
return new MultipartPartBuilder();
}
MultipartPartBuilder(final ContentBody body) {
this();
this.body = body;
}
MultipartPartBuilder() {
this.header = new Header();
}
public MultipartPartBuilder setBody(final ContentBody body) {
this.body = body;
return this;
}
public MultipartPartBuilder addHeader(final String name, final String value, final List<NameValuePair> parameters) {
Args.notNull(name, "Header name");
this.header.addField(new MinimalField(name, value, parameters));
return this;
}
public MultipartPartBuilder addHeader(final String name, final String value) {
Args.notNull(name, "Header name");
this.header.addField(new MinimalField(name, value));
return this;
}
public MultipartPartBuilder setHeader(final String name, final String value) {
Args.notNull(name, "Header name");
this.header.setField(new MinimalField(name, value));
return this;
}
public MultipartPartBuilder removeHeaders(final String name) {
Args.notNull(name, "Header name");
this.header.removeFields(name);
return this;
}
public MultipartPart build() {
Asserts.notNull(this.body, "Content body");
final Header headerCopy = new Header();
final List<MinimalField> fields = this.header.getFields();
for (final MinimalField field: fields) {
headerCopy.addField(field);
}
if (headerCopy.getField(MIME.CONTENT_TYPE) == null) {
final ContentType contentType;
if (body instanceof AbstractContentBody) {
contentType = ((AbstractContentBody) body).getContentType();
} else {
contentType = null;
}
if (contentType != null) {
headerCopy.addField(new MinimalField(MIME.CONTENT_TYPE, contentType.toString()));
} else {
final StringBuilder buffer = new StringBuilder();
buffer.append(this.body.getMimeType()); // MimeType cannot be null
if (this.body.getCharset() != null) { // charset may legitimately be null
buffer.append("; charset=");
buffer.append(this.body.getCharset());
}
headerCopy.addField(new MinimalField(MIME.CONTENT_TYPE, buffer.toString()));
}
}
return new MultipartPart(this.body, headerCopy);
}
}

View File

@ -47,7 +47,7 @@ public class TestMultipartEntityBuilder {
final MultipartFormEntity entity = MultipartEntityBuilder.create().buildEntity(); final MultipartFormEntity entity = MultipartEntityBuilder.create().buildEntity();
Assert.assertNotNull(entity); Assert.assertNotNull(entity);
Assert.assertTrue(entity.getMultipart() instanceof HttpStrictMultipart); Assert.assertTrue(entity.getMultipart() instanceof HttpStrictMultipart);
Assert.assertEquals(0, entity.getMultipart().getBodyParts().size()); Assert.assertEquals(0, entity.getMultipart().getParts().size());
} }
@Test @Test
@ -72,7 +72,7 @@ public class TestMultipartEntityBuilder {
.addBinaryBody("p4", new ByteArrayInputStream(new byte[]{})) .addBinaryBody("p4", new ByteArrayInputStream(new byte[]{}))
.buildEntity(); .buildEntity();
Assert.assertNotNull(entity); Assert.assertNotNull(entity);
final List<FormBodyPart> bodyParts = entity.getMultipart().getBodyParts(); final List<MultipartPart> bodyParts = entity.getMultipart().getParts();
Assert.assertNotNull(bodyParts); Assert.assertNotNull(bodyParts);
Assert.assertEquals(4, bodyParts.size()); Assert.assertEquals(4, bodyParts.size());
} }

View File

@ -65,7 +65,7 @@ public class TestMultipartForm {
"field3", "field3",
new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build(); new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo", final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.asList(p1, p2, p3)); Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -102,7 +102,7 @@ public class TestMultipartForm {
"field2", "field2",
new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build(); new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo", final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.asList(p1, p2)); Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -140,7 +140,7 @@ public class TestMultipartForm {
"field2", "field2",
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build(); new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo", final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.asList(p1, p2)); Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -183,7 +183,7 @@ public class TestMultipartForm {
"field3", "field3",
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build(); new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo", final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.asList(p1, p2, p3)); Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -232,7 +232,7 @@ public class TestMultipartForm {
"field3", "field3",
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build(); new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo", final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
Arrays.asList(p1, p2, p3)); Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -302,7 +302,7 @@ public class TestMultipartForm {
new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build(); new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
final HttpBrowserCompatibleMultipart multipart = new HttpBrowserCompatibleMultipart( final HttpBrowserCompatibleMultipart multipart = new HttpBrowserCompatibleMultipart(
StandardCharsets.UTF_8, "foo", StandardCharsets.UTF_8, "foo",
Arrays.asList(p1, p2)); Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out); multipart.writeTo(out);
@ -339,7 +339,7 @@ public class TestMultipartForm {
"field2", "field2",
new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build(); new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo", final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.asList(p1, p2)); Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out1 = new ByteArrayOutputStream(); final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
multipart.writeTo(out1); multipart.writeTo(out1);

View File

@ -54,7 +54,7 @@ public class TestMultipartFormHttpEntity {
final String contentType = entity.getContentType(); final String contentType = entity.getContentType();
final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType, final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType,
new ParserCursor(0, contentType.length())); new ParserCursor(0, contentType.length()));
Assert.assertEquals("multipart/form-data", elem.getName()); Assert.assertEquals("multipart/mixed", elem.getName());
final NameValuePair p1 = elem.getParameterByName("boundary"); final NameValuePair p1 = elem.getParameterByName("boundary");
Assert.assertNotNull(p1); Assert.assertNotNull(p1);
Assert.assertEquals("whatever", p1.getValue()); Assert.assertEquals("whatever", p1.getValue());
@ -70,7 +70,7 @@ public class TestMultipartFormHttpEntity {
final String contentType = entity.getContentType(); final String contentType = entity.getContentType();
final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType, final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType,
new ParserCursor(0, contentType.length())); new ParserCursor(0, contentType.length()));
Assert.assertEquals("multipart/form-data", elem.getName()); Assert.assertEquals("multipart/mixed", elem.getName());
final NameValuePair p1 = elem.getParameterByName("boundary"); final NameValuePair p1 = elem.getParameterByName("boundary");
Assert.assertNotNull(p1); Assert.assertNotNull(p1);

View File

@ -0,0 +1,332 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.entity.mime;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.apache.hc.core5.http.ContentType;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
public class TestMultipartMixed {
private File tmpfile;
@After
public void cleanup() {
if (tmpfile != null) {
tmpfile.delete();
}
}
@Test
public void testMultipartPartStringParts() throws Exception {
final MultipartPart p1 = MultipartPartBuilder.create(
new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
final MultipartPart p2 = MultipartPartBuilder.create(
new StringBody("that stuff", ContentType.create(
ContentType.TEXT_PLAIN.getMimeType(), StandardCharsets.UTF_8))).build();
final MultipartPart p3 = MultipartPartBuilder.create(
new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: text/plain; charset=ISO-8859-1\r\n" +
"\r\n" +
"this stuff\r\n" +
"--foo\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"that stuff\r\n" +
"--foo\r\n" +
"Content-Type: text/plain; charset=ISO-8859-1\r\n" +
"\r\n" +
"all kind of stuff\r\n" +
"--foo--\r\n";
final String s = out.toString("US-ASCII");
Assert.assertEquals(expected, s);
Assert.assertEquals(s.length(), multipart.getTotalLength());
}
@Test
public void testMultipartPartCustomContentType() throws Exception {
final MultipartPart p1 = MultipartPartBuilder.create(
new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
final MultipartPart p2 = MultipartPartBuilder.create(
new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: text/plain; charset=ISO-8859-1\r\n" +
"\r\n" +
"this stuff\r\n" +
"--foo\r\n" +
"Content-Type: stuff/plain; param=value\r\n" +
"\r\n" +
"that stuff\r\n" +
"--foo--\r\n";
final String s = out.toString("US-ASCII");
Assert.assertEquals(expected, s);
Assert.assertEquals(s.length(), multipart.getTotalLength());
}
@Test
public void testMultipartPartBinaryParts() throws Exception {
tmpfile = File.createTempFile("tmp", ".bin");
try (Writer writer = new FileWriter(tmpfile)) {
writer.append("some random whatever");
}
final MultipartPart p1 = MultipartPartBuilder.create(
new FileBody(tmpfile)).build();
@SuppressWarnings("resource")
final MultipartPart p2 = MultipartPartBuilder.create(
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo--\r\n";
final String s = out.toString("US-ASCII");
Assert.assertEquals(expected, s);
Assert.assertEquals(-1, multipart.getTotalLength());
}
@Test
public void testMultipartPartStrict() throws Exception {
tmpfile = File.createTempFile("tmp", ".bin");
try (Writer writer = new FileWriter(tmpfile)) {
writer.append("some random whatever");
}
final MultipartPart p1 = MultipartPartBuilder.create(
new FileBody(tmpfile)).build();
final MultipartPart p2 = MultipartPartBuilder.create(
new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
@SuppressWarnings("resource")
final MultipartPart p3 = MultipartPartBuilder.create(
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo--\r\n";
final String s = out.toString("US-ASCII");
Assert.assertEquals(expected, s);
Assert.assertEquals(-1, multipart.getTotalLength());
}
@Test
public void testMultipartPartRFC6532() throws Exception {
tmpfile = File.createTempFile("tmp", ".bin");
try (Writer writer = new FileWriter(tmpfile)) {
writer.append("some random whatever");
}
final MultipartPart p1 = MultipartPartBuilder.create(
new FileBody(tmpfile)).build();
final MultipartPart p2 = MultipartPartBuilder.create(
new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
@SuppressWarnings("resource")
final MultipartPart p3 = MultipartPartBuilder.create(
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2, p3));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo--\r\n";
final String s = out.toString("UTF-8");
Assert.assertEquals(expected, s);
Assert.assertEquals(-1, multipart.getTotalLength());
}
private static final int SWISS_GERMAN_HELLO [] = {
0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
};
private static final int RUSSIAN_HELLO [] = {
0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
0x432, 0x435, 0x442
};
private static String constructString(final int [] unicodeChars) {
final StringBuilder buffer = new StringBuilder();
if (unicodeChars != null) {
for (final int unicodeChar : unicodeChars) {
buffer.append((char)unicodeChar);
}
}
return buffer.toString();
}
@Test
public void testMultipartPartBrowserCompatibleNonASCIIHeaders() throws Exception {
final String s1 = constructString(SWISS_GERMAN_HELLO);
final String s2 = constructString(RUSSIAN_HELLO);
tmpfile = File.createTempFile("tmp", ".bin");
try (Writer writer = new FileWriter(tmpfile)) {
writer.append("some random whatever");
}
@SuppressWarnings("resource")
final MultipartPart p1 = MultipartPartBuilder.create(
new InputStreamBody(new FileInputStream(tmpfile), s1 + ".tmp")).build();
@SuppressWarnings("resource")
final MultipartPart p2 = MultipartPartBuilder.create(
new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
final HttpBrowserCompatibleMultipart multipart = new HttpBrowserCompatibleMultipart(
StandardCharsets.UTF_8, "foo",
Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
multipart.writeTo(out);
out.close();
final String expected =
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"some random whatever\r\n" +
"--foo--\r\n";
final String s = out.toString("UTF-8");
Assert.assertEquals(expected, s);
Assert.assertEquals(-1, multipart.getTotalLength());
}
@Test
public void testMultipartPartStringPartsMultiCharsets() throws Exception {
final String s1 = constructString(SWISS_GERMAN_HELLO);
final String s2 = constructString(RUSSIAN_HELLO);
final MultipartPart p1 = MultipartPartBuilder.create(
new StringBody(s1, ContentType.create("text/plain", Charset.forName("ISO-8859-1")))).build();
final MultipartPart p2 = MultipartPartBuilder.create(
new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build();
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
Arrays.<MultipartPart>asList(p1, p2));
final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
multipart.writeTo(out1);
out1.close();
final ByteArrayOutputStream out2 = new ByteArrayOutputStream();
out2.write((
"--foo\r\n" +
"Content-Type: text/plain; charset=ISO-8859-1\r\n" +
"\r\n").getBytes(StandardCharsets.US_ASCII));
out2.write(s1.getBytes(StandardCharsets.ISO_8859_1));
out2.write(("\r\n" +
"--foo\r\n" +
"Content-Type: text/plain; charset=KOI8-R\r\n" +
"\r\n").getBytes(StandardCharsets.US_ASCII));
out2.write(s2.getBytes(Charset.forName("KOI8-R")));
out2.write(("\r\n" +
"--foo--\r\n").getBytes(StandardCharsets.US_ASCII));
out2.close();
final byte[] actual = out1.toByteArray();
final byte[] expected = out2.toByteArray();
Assert.assertEquals(expected.length, actual.length);
for (int i = 0; i < actual.length; i++) {
Assert.assertEquals(expected[i], actual[i]);
}
Assert.assertEquals(expected.length, multipart.getTotalLength());
}
}

View File

@ -0,0 +1,157 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.entity.mime;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import org.apache.hc.core5.http.ContentType;
import org.junit.Assert;
import org.junit.Test;
public class TestMultipartPartBuilder {
@Test
public void testBuildBodyPartBasics() throws Exception {
final StringBody stringBody = new StringBody("stuff", ContentType.TEXT_PLAIN);
final MultipartPart part = MultipartPartBuilder.create()
.setBody(stringBody)
.build();
Assert.assertNotNull(part);
Assert.assertEquals(stringBody, part.getBody());
final Header header = part.getHeader();
Assert.assertNotNull(header);
assertFields(Arrays.asList(
new MinimalField("Content-Type", "text/plain; charset=ISO-8859-1")),
header.getFields());
}
@Test
public void testBuildBodyPartMultipleBuilds() throws Exception {
final StringBody stringBody = new StringBody("stuff", ContentType.TEXT_PLAIN);
final MultipartPartBuilder builder = MultipartPartBuilder.create();
final MultipartPart part1 = builder
.setBody(stringBody)
.build();
Assert.assertNotNull(part1);
Assert.assertEquals(stringBody, part1.getBody());
final Header header1 = part1.getHeader();
Assert.assertNotNull(header1);
assertFields(Arrays.asList(
new MinimalField("Content-Type", "text/plain; charset=ISO-8859-1")),
header1.getFields());
final FileBody fileBody = new FileBody(new File("/path/stuff.bin"), ContentType.DEFAULT_BINARY);
final MultipartPart part2 = builder
.setBody(fileBody)
.build();
Assert.assertNotNull(part2);
Assert.assertEquals(fileBody, part2.getBody());
final Header header2 = part2.getHeader();
Assert.assertNotNull(header2);
assertFields(Arrays.asList(
new MinimalField("Content-Type", "application/octet-stream")),
header2.getFields());
}
@Test
public void testBuildBodyPartCustomHeaders() throws Exception {
final StringBody stringBody = new StringBody("stuff", ContentType.TEXT_PLAIN);
final MultipartPartBuilder builder = MultipartPartBuilder.create(stringBody);
final MultipartPart part1 = builder
.addHeader("header1", "blah")
.addHeader("header3", "blah")
.addHeader("header3", "blah")
.addHeader("header3", "blah")
.addHeader("header3", "blah")
.addHeader("header3", "blah")
.build();
Assert.assertNotNull(part1);
final Header header1 = part1.getHeader();
Assert.assertNotNull(header1);
assertFields(Arrays.asList(
new MinimalField("header1", "blah"),
new MinimalField("header3", "blah"),
new MinimalField("header3", "blah"),
new MinimalField("header3", "blah"),
new MinimalField("header3", "blah"),
new MinimalField("header3", "blah"),
new MinimalField("Content-Type", "text/plain; charset=ISO-8859-1")),
header1.getFields());
final MultipartPart part2 = builder
.addHeader("header2", "yada")
.removeHeaders("header3")
.build();
Assert.assertNotNull(part2);
final Header header2 = part2.getHeader();
Assert.assertNotNull(header2);
assertFields(Arrays.asList(
new MinimalField("header1", "blah"),
new MinimalField("header2", "yada"),
new MinimalField("Content-Type", "text/plain; charset=ISO-8859-1")),
header2.getFields());
final MultipartPart part3 = builder
.addHeader("Content-Disposition", "disposition stuff")
.addHeader("Content-Type", "type stuff")
.addHeader("Content-Transfer-Encoding", "encoding stuff")
.build();
Assert.assertNotNull(part3);
final Header header3 = part3.getHeader();
Assert.assertNotNull(header3);
assertFields(Arrays.asList(
new MinimalField("header1", "blah"),
new MinimalField("header2", "yada"),
new MinimalField("Content-Disposition", "disposition stuff"),
new MinimalField("Content-Type", "type stuff"),
new MinimalField("Content-Transfer-Encoding", "encoding stuff")),
header3.getFields());
}
private static void assertFields(final List<MinimalField> expected, final List<MinimalField> result) {
Assert.assertNotNull(result);
Assert.assertEquals(expected.size(), result.size());
for (int i = 0; i < expected.size(); i++) {
final MinimalField expectedField = expected.get(i);
final MinimalField resultField = result.get(i);
Assert.assertNotNull(resultField);
Assert.assertEquals(expectedField.getName(), resultField.getName());
Assert.assertEquals(expectedField.getBody(), resultField.getBody());
}
}
}