You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2019/06/03 13:33:31 UTC

[httpcomponents-client] branch master updated: Refactor to enable support for non-form based multipart requests

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new d71d0f5  Refactor to enable support for non-form based multipart requests
d71d0f5 is described below

commit d71d0f5a4cecf0c620f9110cb567524416100895
Author: Adam Retter <ad...@googlemail.com>
AuthorDate: Sun Jun 2 15:47:30 2019 +0100

    Refactor to enable support for non-form based multipart requests
---
 ...ipartForm.java => AbstractMultipartFormat.java} |  14 +-
 .../hc/client5/http/entity/mime/FormBodyPart.java  |  18 +--
 .../mime/HttpBrowserCompatibleMultipart.java       |  14 +-
 .../http/entity/mime/HttpRFC6532Multipart.java     |  10 +-
 .../http/entity/mime/HttpRFC7578Multipart.java     |  10 +-
 .../http/entity/mime/HttpStrictMultipart.java      |  10 +-
 .../http/entity/mime/MultipartEntityBuilder.java   |  55 +++++---
 .../http/entity/mime/MultipartFormEntity.java      |   6 +-
 .../mime/{FormBodyPart.java => MultipartPart.java} |  26 ++--
 .../http/entity/mime/MultipartPartBuilder.java     | 121 ++++++++++++++++
 .../entity/mime/TestMultipartEntityBuilder.java    |   4 +-
 .../http/entity/mime/TestMultipartForm.java        |  14 +-
 .../entity/mime/TestMultipartFormHttpEntity.java   |   4 +-
 ...tMultipartForm.java => TestMultipartMixed.java} | 108 +++++---------
 .../http/entity/mime/TestMultipartPartBuilder.java | 157 +++++++++++++++++++++
 15 files changed, 403 insertions(+), 168 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartForm.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java
similarity index 94%
rename from httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartForm.java
rename to httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java
index b36afc8..93befa6 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartForm.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java
@@ -46,7 +46,7 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
  *
  * @since 4.3
  */
-abstract class AbstractMultipartForm {
+abstract class AbstractMultipartFormat {
 
     static ByteArrayBuffer encode(
             final Charset charset, final String string) {
@@ -103,25 +103,25 @@ abstract class AbstractMultipartForm {
      * @param boundary to use  - must not be {@code 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();
         Args.notNull(boundary, "Multipart boundary");
         this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1;
         this.boundary = boundary;
     }
 
-    public AbstractMultipartForm(final String boundary) {
+    public AbstractMultipartFormat(final String boundary) {
         this(null, boundary);
     }
 
-    public abstract List<FormBodyPart> getBodyParts();
+    public abstract List<MultipartPart> getParts();
 
     void doWriteTo(
         final OutputStream out,
         final boolean writeContent) throws IOException {
 
         final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
-        for (final FormBodyPart part: getBodyParts()) {
+        for (final MultipartPart part: getParts()) {
             writeBytes(TWO_DASHES, out);
             writeBytes(boundaryEncoded, out);
             writeBytes(CR_LF, out);
@@ -145,7 +145,7 @@ abstract class AbstractMultipartForm {
       * Write the multipart header fields; depends on the style.
       */
     protected abstract void formatMultipartHeader(
-        final FormBodyPart part,
+        final MultipartPart part,
         final OutputStream out) throws IOException;
 
     /**
@@ -173,7 +173,7 @@ abstract class AbstractMultipartForm {
      */
     public long getTotalLength() {
         long contentLen = 0;
-        for (final FormBodyPart part: getBodyParts()) {
+        for (final MultipartPart part: getParts()) {
             final ContentBody body = part.getBody();
             final long len = body.getContentLength();
             if (len >= 0) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java
index 4f585a7..9f3e926 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java
@@ -36,36 +36,24 @@ import org.apache.hc.core5.util.Args;
  *
  * @since 4.0
  */
-public class FormBodyPart {
+public class FormBodyPart extends MultipartPart {
 
     private final String name;
-    private final Header header;
-    private final ContentBody body;
 
     FormBodyPart(final String name, final ContentBody body, final Header header) {
-        super();
+        super(body, header);
         Args.notNull(name, "Name");
         Args.notNull(body, "Body");
         this.name = name;
-        this.body = body;
-        this.header = header != null ? header : new Header();
     }
 
     public String getName() {
         return this.name;
     }
 
-    public ContentBody getBody() {
-        return this.body;
-    }
-
-    public Header getHeader() {
-        return this.header;
-    }
-
     public void addField(final String name, final String value) {
         Args.notNull(name, "Field name");
-        this.header.addField(new MinimalField(name, value));
+        super.addField(name, value);
     }
 
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpBrowserCompatibleMultipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpBrowserCompatibleMultipart.java
index b252ae5..f16d593 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpBrowserCompatibleMultipart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpBrowserCompatibleMultipart.java
@@ -38,20 +38,20 @@ import java.util.List;
  *
  * @since 4.3
  */
-class HttpBrowserCompatibleMultipart extends AbstractMultipartForm {
+class HttpBrowserCompatibleMultipart extends AbstractMultipartFormat {
 
-    private final List<FormBodyPart> parts;
+    private final List<MultipartPart> parts;
 
     public HttpBrowserCompatibleMultipart(
             final Charset charset,
             final String boundary,
-            final List<FormBodyPart> parts) {
+            final List<MultipartPart> parts) {
         super(charset, boundary);
         this.parts = parts;
     }
 
     @Override
-    public List<FormBodyPart> getBodyParts() {
+    public List<MultipartPart> getParts() {
         return this.parts;
     }
 
@@ -60,13 +60,15 @@ class HttpBrowserCompatibleMultipart extends AbstractMultipartForm {
       */
     @Override
     protected void formatMultipartHeader(
-            final FormBodyPart part,
+            final MultipartPart part,
             final OutputStream out) throws IOException {
         // For browser-compatible, only write Content-Disposition
         // Use content charset
         final Header header = part.getHeader();
         final MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION);
-        writeField(cd, this.charset, out);
+        if (cd != null) {
+            writeField(cd, this.charset, out);
+        }
         final String filename = part.getBody().getFilename();
         if (filename != null) {
             final MinimalField ct = header.getField(MIME.CONTENT_TYPE);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC6532Multipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC6532Multipart.java
index 8c13948..40dc156 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC6532Multipart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC6532Multipart.java
@@ -40,26 +40,26 @@ import java.util.List;
  *
  * @since 4.3
  */
-class HttpRFC6532Multipart extends AbstractMultipartForm {
+class HttpRFC6532Multipart extends AbstractMultipartFormat {
 
-    private final List<FormBodyPart> parts;
+    private final List<MultipartPart> parts;
 
     public HttpRFC6532Multipart(
             final Charset charset,
             final String boundary,
-            final List<FormBodyPart> parts) {
+            final List<MultipartPart> parts) {
         super(charset, boundary);
         this.parts = parts;
     }
 
     @Override
-    public List<FormBodyPart> getBodyParts() {
+    public List<MultipartPart> getParts() {
         return this.parts;
     }
 
     @Override
     protected void formatMultipartHeader(
-        final FormBodyPart part,
+        final MultipartPart part,
         final OutputStream out) throws IOException {
 
         // For RFC6532, we output all fields with UTF-8 encoding.
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
index e3437f5..8249575 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
@@ -40,27 +40,27 @@ import org.apache.commons.codec.DecoderException;
 import org.apache.hc.core5.http.NameValuePair;
 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 final List<FormBodyPart> parts;
+    private final List<MultipartPart> parts;
 
     public HttpRFC7578Multipart(
         final Charset charset,
         final String boundary,
-        final List<FormBodyPart> parts) {
+        final List<MultipartPart> parts) {
         super(charset, boundary);
         this.parts = parts;
     }
 
     @Override
-    public List<FormBodyPart> getBodyParts() {
+    public List<MultipartPart> getParts() {
         return parts;
     }
 
     @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()) {
             if (MIME.CONTENT_DISPOSITION.equalsIgnoreCase(field.getName())) {
                 writeBytes(field.getName(), charset, out);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpStrictMultipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpStrictMultipart.java
index 499d82e..1c7a017 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpStrictMultipart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpStrictMultipart.java
@@ -39,26 +39,26 @@ import java.util.List;
  *
  * @since 4.3
  */
-class HttpStrictMultipart extends AbstractMultipartForm {
+class HttpStrictMultipart extends AbstractMultipartFormat {
 
-    private final List<FormBodyPart> parts;
+    private final List<MultipartPart> parts;
 
     public HttpStrictMultipart(
             final Charset charset,
             final String boundary,
-            final List<FormBodyPart> parts) {
+            final List<MultipartPart> parts) {
         super(charset, boundary);
         this.parts = parts;
     }
 
     @Override
-    public List<FormBodyPart> getBodyParts() {
+    public List<MultipartPart> getParts() {
         return this.parts;
     }
 
     @Override
     protected void formatMultipartHeader(
-        final FormBodyPart part,
+        final MultipartPart part,
         final OutputStream out) throws IOException {
 
         // For strict, we output all fields with MIME-standard encoding.
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartEntityBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartEntityBuilder.java
index 441b1c5..86f24c9 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartEntityBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartEntityBuilder.java
@@ -45,7 +45,7 @@ import org.apache.hc.core5.util.Args;
 /**
  * Builder for multipart {@link HttpEntity}s.
  *
- * @since 4.3
+ * @since 5.0
  */
 public class MultipartEntityBuilder {
 
@@ -56,13 +56,14 @@ public class MultipartEntityBuilder {
             "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
                     .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 HttpMultipartMode mode = HttpMultipartMode.STRICT;
     private String boundary = null;
     private Charset charset = null;
-    private List<FormBodyPart> bodyParts = null;
+    private List<MultipartPart> multipartParts = null;
 
     public static MultipartEntityBuilder create() {
         return new MultipartEntityBuilder();
@@ -117,14 +118,14 @@ public class MultipartEntityBuilder {
     /**
      * @since 4.4
      */
-    public MultipartEntityBuilder addPart(final FormBodyPart bodyPart) {
-        if (bodyPart == null) {
+    public MultipartEntityBuilder addPart(final MultipartPart multipartPart) {
+        if (multipartPart == null) {
             return this;
         }
-        if (this.bodyParts == null) {
-            this.bodyParts = new ArrayList<>();
+        if (this.multipartParts == null) {
+            this.multipartParts = new ArrayList<>();
         }
-        this.bodyParts.add(bodyPart);
+        this.multipartParts.add(multipartPart);
         return this;
     }
 
@@ -202,28 +203,46 @@ public class MultipartEntityBuilder {
             paramsList.add(new BasicNameValuePair("charset", charsetCopy.name()));
         }
         final NameValuePair[] params = paramsList.toArray(new NameValuePair[paramsList.size()]);
-        final ContentType contentTypeCopy = contentType != null ?
-                contentType.withParameters(params) :
-                ContentType.create("multipart/" + DEFAULT_SUBTYPE, params);
-        final List<FormBodyPart> bodyPartsCopy = bodyParts != null ? new ArrayList<>(bodyParts) :
-                Collections.<FormBodyPart>emptyList();
+
+        final ContentType contentTypeCopy;
+        if (contentType != null) {
+            contentTypeCopy = contentType.withParameters(params);
+        } 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 AbstractMultipartForm form;
+        final AbstractMultipartFormat form;
         switch (modeCopy) {
             case BROWSER_COMPATIBLE:
-                form = new HttpBrowserCompatibleMultipart(charsetCopy, boundaryCopy, bodyPartsCopy);
+                form = new HttpBrowserCompatibleMultipart(charsetCopy, boundaryCopy, multipartPartsCopy);
                 break;
             case RFC6532:
-                form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, bodyPartsCopy);
+                form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
                 break;
             case RFC7578:
                 if (charsetCopy == null) {
                     charsetCopy = StandardCharsets.UTF_8;
                 }
-                form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, bodyPartsCopy);
+                form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
                 break;
             default:
-                form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, bodyPartsCopy);
+                form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, multipartPartsCopy);
         }
         return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartFormEntity.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartFormEntity.java
index 9615fc5..73f9141 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartFormEntity.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartFormEntity.java
@@ -43,12 +43,12 @@ import org.apache.hc.core5.http.HttpEntity;
 
 class MultipartFormEntity implements HttpEntity {
 
-    private final AbstractMultipartForm multipart;
+    private final AbstractMultipartFormat multipart;
     private final ContentType contentType;
     private final long contentLength;
 
     MultipartFormEntity(
-            final AbstractMultipartForm multipart,
+            final AbstractMultipartFormat multipart,
             final ContentType contentType,
             final long contentLength) {
         super();
@@ -57,7 +57,7 @@ class MultipartFormEntity implements HttpEntity {
         this.contentLength = contentLength;
     }
 
-    AbstractMultipartForm getMultipart() {
+    AbstractMultipartFormat getMultipart() {
         return this.multipart;
     }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPart.java
similarity index 73%
copy from httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java
copy to httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPart.java
index 4f585a7..05ebf77 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/FormBodyPart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPart.java
@@ -27,34 +27,24 @@
 
 package org.apache.hc.client5.http.entity.mime;
 
-import org.apache.hc.core5.util.Args;
-
 /**
- * FormBodyPart class represents a content body that can be used as a part of multipart encoded
+ * 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 4.0
+ * @since 5.0
  */
-public class FormBodyPart {
+public class MultipartPart {
 
-    private final String name;
     private final Header header;
     private final ContentBody body;
 
-    FormBodyPart(final String name, final ContentBody body, final Header header) {
+    MultipartPart(final ContentBody body, final Header header) {
         super();
-        Args.notNull(name, "Name");
-        Args.notNull(body, "Body");
-        this.name = name;
         this.body = body;
         this.header = header != null ? header : new Header();
     }
 
-    public String getName() {
-        return this.name;
-    }
-
     public ContentBody getBody() {
         return this.body;
     }
@@ -63,9 +53,11 @@ public class FormBodyPart {
         return this.header;
     }
 
-    public void addField(final String name, final String value) {
-        Args.notNull(name, "Field name");
-        this.header.addField(new MinimalField(name, value));
+    void addField(final String name, final String value) {
+        addField(new MinimalField(name, value));
     }
 
+    void addField(final MinimalField field) {
+        this.header.addField(field);
+    }
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPartBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPartBuilder.java
new file mode 100644
index 0000000..e5ef448
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartPartBuilder.java
@@ -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);
+    }
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartEntityBuilder.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartEntityBuilder.java
index 16b1271..937a89d 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartEntityBuilder.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartEntityBuilder.java
@@ -47,7 +47,7 @@ public class TestMultipartEntityBuilder {
         final MultipartFormEntity entity = MultipartEntityBuilder.create().buildEntity();
         Assert.assertNotNull(entity);
         Assert.assertTrue(entity.getMultipart() instanceof HttpStrictMultipart);
-        Assert.assertEquals(0, entity.getMultipart().getBodyParts().size());
+        Assert.assertEquals(0, entity.getMultipart().getParts().size());
     }
 
     @Test
@@ -72,7 +72,7 @@ public class TestMultipartEntityBuilder {
                 .addBinaryBody("p4", new ByteArrayInputStream(new byte[]{}))
                 .buildEntity();
         Assert.assertNotNull(entity);
-        final List<FormBodyPart> bodyParts = entity.getMultipart().getBodyParts();
+        final List<MultipartPart> bodyParts = entity.getMultipart().getParts();
         Assert.assertNotNull(bodyParts);
         Assert.assertEquals(4, bodyParts.size());
     }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java
index 90bf5ca..98d6ee6 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java
@@ -65,7 +65,7 @@ public class TestMultipartForm {
                 "field3",
                 new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -102,7 +102,7 @@ public class TestMultipartForm {
                 "field2",
                 new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -140,7 +140,7 @@ public class TestMultipartForm {
                 "field2",
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -183,7 +183,7 @@ public class TestMultipartForm {
                 "field3",
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -232,7 +232,7 @@ public class TestMultipartForm {
                 "field3",
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -302,7 +302,7 @@ public class TestMultipartForm {
                 new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
         final HttpBrowserCompatibleMultipart multipart = new HttpBrowserCompatibleMultipart(
                 StandardCharsets.UTF_8, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -339,7 +339,7 @@ public class TestMultipartForm {
                 "field2",
                 new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
         multipart.writeTo(out1);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormHttpEntity.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormHttpEntity.java
index 8838e59..76ea284 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormHttpEntity.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormHttpEntity.java
@@ -54,7 +54,7 @@ public class TestMultipartFormHttpEntity {
         final String contentType = entity.getContentType();
         final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType,
                 new ParserCursor(0, contentType.length()));
-        Assert.assertEquals("multipart/form-data", elem.getName());
+        Assert.assertEquals("multipart/mixed", elem.getName());
         final NameValuePair p1 = elem.getParameterByName("boundary");
         Assert.assertNotNull(p1);
         Assert.assertEquals("whatever", p1.getValue());
@@ -70,7 +70,7 @@ public class TestMultipartFormHttpEntity {
         final String contentType = entity.getContentType();
         final HeaderElement elem = BasicHeaderValueParser.INSTANCE.parseHeaderElement(contentType,
                 new ParserCursor(0, contentType.length()));
-        Assert.assertEquals("multipart/form-data", elem.getName());
+        Assert.assertEquals("multipart/mixed", elem.getName());
         final NameValuePair p1 = elem.getParameterByName("boundary");
         Assert.assertNotNull(p1);
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartMixed.java
similarity index 74%
copy from httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java
copy to httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartMixed.java
index 90bf5ca..714d5ff 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartForm.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartMixed.java
@@ -41,7 +41,7 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class TestMultipartForm {
+public class TestMultipartMixed {
 
     private File tmpfile;
 
@@ -53,19 +53,16 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormStringParts() throws Exception {
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+    public void testMultipartPartStringParts() throws Exception {
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new StringBody("that stuff", ContentType.create(
                         ContentType.TEXT_PLAIN.getMimeType(), StandardCharsets.UTF_8))).build();
-        final FormBodyPart p3 = FormBodyPartBuilder.create(
-                "field3",
+        final MultipartPart p3 = MultipartPartBuilder.create(
                 new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -73,17 +70,14 @@ public class TestMultipartForm {
 
         final String expected =
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\"\r\n" +
             "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
             "\r\n" +
             "this stuff\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field2\"\r\n" +
             "Content-Type: text/plain; charset=UTF-8\r\n" +
             "\r\n" +
             "that stuff\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field3\"\r\n" +
             "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
             "\r\n" +
             "all kind of stuff\r\n" +
@@ -94,15 +88,13 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormCustomContentType() throws Exception {
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+    public void testMultipartPartCustomContentType() throws Exception {
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -110,12 +102,10 @@ public class TestMultipartForm {
 
         final String expected =
                 "--foo\r\n" +
-                        "Content-Disposition: form-data; name=\"field1\"\r\n" +
                         "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
                         "\r\n" +
                         "this stuff\r\n" +
                         "--foo\r\n" +
-                        "Content-Disposition: form-data; name=\"field2\"\r\n" +
                         "Content-Type: stuff/plain; param=value\r\n" +
                         "\r\n" +
                         "that stuff\r\n" +
@@ -126,21 +116,19 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormBinaryParts() throws Exception {
+    public void testMultipartPartBinaryParts() throws Exception {
         tmpfile = File.createTempFile("tmp", ".bin");
         try (Writer writer = new FileWriter(tmpfile)) {
             writer.append("some random whatever");
         }
 
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new FileBody(tmpfile)).build();
         @SuppressWarnings("resource")
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -148,14 +136,10 @@ public class TestMultipartForm {
 
         final String expected =
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\"; " +
-                "filename=\"" + tmpfile.getName() + "\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field2\"; " +
-                "filename=\"file.tmp\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
@@ -166,24 +150,21 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormStrict() throws Exception {
+    public void testMultipartPartStrict() throws Exception {
         tmpfile = File.createTempFile("tmp", ".bin");
         try (Writer writer = new FileWriter(tmpfile)) {
             writer.append("some random whatever");
         }
 
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new FileBody(tmpfile)).build();
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
         @SuppressWarnings("resource")
-        final FormBodyPart p3 = FormBodyPartBuilder.create(
-                "field3",
+        final MultipartPart p3 = MultipartPartBuilder.create(
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -191,20 +172,14 @@ public class TestMultipartForm {
 
         final String expected =
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\"; " +
-                "filename=\"" + tmpfile.getName() + "\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field2\"; " +
-                "filename=\"test-file\"\r\n" +
             "Content-Type: text/plain; charset=US-ASCII\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field3\"; " +
-                "filename=\"file.tmp\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
@@ -215,24 +190,21 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormRFC6532() throws Exception {
+    public void testMultipartPartRFC6532() throws Exception {
         tmpfile = File.createTempFile("tmp", ".bin");
         try (Writer writer = new FileWriter(tmpfile)) {
             writer.append("some random whatever");
         }
 
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1\u0414",
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new FileBody(tmpfile)).build();
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
         @SuppressWarnings("resource")
-        final FormBodyPart p3 = FormBodyPartBuilder.create(
-                "field3",
+        final MultipartPart p3 = MultipartPartBuilder.create(
                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
         final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
-                Arrays.asList(p1, p2, p3));
+                Arrays.<MultipartPart>asList(p1, p2, p3));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -240,20 +212,14 @@ public class TestMultipartForm {
 
         final String expected =
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\u0414\"; " +
-                "filename=\"" + tmpfile.getName() + "\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field2\"; " +
-                "filename=\"test-file\"\r\n" +
             "Content-Type: text/plain; charset=US-ASCII\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field3\"; " +
-                "filename=\"file.tmp\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
@@ -283,7 +249,7 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormBrowserCompatibleNonASCIIHeaders() throws Exception {
+    public void testMultipartPartBrowserCompatibleNonASCIIHeaders() throws Exception {
         final String s1 = constructString(SWISS_GERMAN_HELLO);
         final String s2 = constructString(RUSSIAN_HELLO);
 
@@ -293,16 +259,14 @@ public class TestMultipartForm {
         }
 
         @SuppressWarnings("resource")
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new InputStreamBody(new FileInputStream(tmpfile), s1 + ".tmp")).build();
         @SuppressWarnings("resource")
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        final MultipartPart p2 = MultipartPartBuilder.create(
                 new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
         final HttpBrowserCompatibleMultipart multipart = new HttpBrowserCompatibleMultipart(
                 StandardCharsets.UTF_8, "foo",
-                Arrays.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         multipart.writeTo(out);
@@ -310,14 +274,10 @@ public class TestMultipartForm {
 
         final String expected =
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\"; " +
-                "filename=\"" + s1 + ".tmp\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field2\"; " +
-                "filename=\"" + s2 + ".tmp\"\r\n" +
             "Content-Type: application/octet-stream\r\n" +
             "\r\n" +
             "some random whatever\r\n" +
@@ -328,18 +288,16 @@ public class TestMultipartForm {
     }
 
     @Test
-    public void testMultipartFormStringPartsMultiCharsets() throws Exception {
+    public void testMultipartPartStringPartsMultiCharsets() throws Exception {
         final String s1 = constructString(SWISS_GERMAN_HELLO);
         final String s2 = constructString(RUSSIAN_HELLO);
 
-        final FormBodyPart p1 = FormBodyPartBuilder.create(
-                "field1",
+        final MultipartPart p1 = MultipartPartBuilder.create(
                 new StringBody(s1, ContentType.create("text/plain", Charset.forName("ISO-8859-1")))).build();
-        final FormBodyPart p2 = FormBodyPartBuilder.create(
-                "field2",
+        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.asList(p1, p2));
+                Arrays.<MultipartPart>asList(p1, p2));
 
         final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
         multipart.writeTo(out1);
@@ -349,13 +307,11 @@ public class TestMultipartForm {
 
         out2.write((
             "--foo\r\n" +
-            "Content-Disposition: form-data; name=\"field1\"\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-Disposition: form-data; name=\"field2\"\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")));
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartPartBuilder.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartPartBuilder.java
new file mode 100644
index 0000000..6304dd7
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartPartBuilder.java
@@ -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());
+        }
+    }
+
+}