1 /*
2  * ====================================================================
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  * ====================================================================
20  *
21  * This software consists of voluntary contributions made by many
22  * individuals on behalf of the Apache Software Foundation.  For more
23  * information on the Apache Software Foundation, please see
24  * <http://www.apache.org/>.
25  *
26  */
27 
28 package ch.boye.httpclientandroidlib.entity.mime;
29 
30 import java.io.File;
31 import java.io.InputStream;
32 import java.nio.charset.Charset;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Random;
37 
38 import ch.boye.httpclientandroidlib.HttpEntity;
39 import ch.boye.httpclientandroidlib.entity.ContentType;
40 import ch.boye.httpclientandroidlib.entity.mime.content.ByteArrayBody;
41 import ch.boye.httpclientandroidlib.entity.mime.content.ContentBody;
42 import ch.boye.httpclientandroidlib.entity.mime.content.FileBody;
43 import ch.boye.httpclientandroidlib.entity.mime.content.InputStreamBody;
44 import ch.boye.httpclientandroidlib.entity.mime.content.StringBody;
45 import ch.boye.httpclientandroidlib.util.Args;
46 
47 /**
48  * Builder for multipart {@link HttpEntity}s.
49  *
50  * @since 4.3
51  */
52 public class MultipartEntityBuilder {
53 
54     /**
55      * The pool of ASCII chars to be used for generating a multipart boundary.
56      */
57     private final static char[] MULTIPART_CHARS =
58             "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
59                     .toCharArray();
60 
61     private final static String DEFAULT_SUBTYPE = "form-data";
62 
63     private String subType = DEFAULT_SUBTYPE;
64     private HttpMultipartMode mode = HttpMultipartMode.STRICT;
65     private String boundary = null;
66     private Charset charset = null;
67     private List<FormBodyPart> bodyParts = null;
68 
create()69     public static MultipartEntityBuilder create() {
70         return new MultipartEntityBuilder();
71     }
72 
MultipartEntityBuilder()73     MultipartEntityBuilder() {
74         super();
75     }
76 
setMode(final HttpMultipartMode mode)77     public MultipartEntityBuilder setMode(final HttpMultipartMode mode) {
78         this.mode = mode;
79         return this;
80     }
81 
setLaxMode()82     public MultipartEntityBuilder setLaxMode() {
83         this.mode = HttpMultipartMode.BROWSER_COMPATIBLE;
84         return this;
85     }
86 
setStrictMode()87     public MultipartEntityBuilder setStrictMode() {
88         this.mode = HttpMultipartMode.STRICT;
89         return this;
90     }
91 
setBoundary(final String boundary)92     public MultipartEntityBuilder setBoundary(final String boundary) {
93         this.boundary = boundary;
94         return this;
95     }
96 
setCharset(final Charset charset)97     public MultipartEntityBuilder setCharset(final Charset charset) {
98         this.charset = charset;
99         return this;
100     }
101 
addPart(final FormBodyPart bodyPart)102     MultipartEntityBuilder addPart(final FormBodyPart bodyPart) {
103         if (bodyPart == null) {
104             return this;
105         }
106         if (this.bodyParts == null) {
107             this.bodyParts = new ArrayList<FormBodyPart>();
108         }
109         this.bodyParts.add(bodyPart);
110         return this;
111     }
112 
addPart(final String name, final ContentBody contentBody)113     public MultipartEntityBuilder addPart(final String name, final ContentBody contentBody) {
114         Args.notNull(name, "Name");
115         Args.notNull(contentBody, "Content body");
116         return addPart(new FormBodyPart(name, contentBody));
117     }
118 
addTextBody( final String name, final String text, final ContentType contentType)119     public MultipartEntityBuilder addTextBody(
120             final String name, final String text, final ContentType contentType) {
121         return addPart(name, new StringBody(text, contentType));
122     }
123 
addTextBody( final String name, final String text)124     public MultipartEntityBuilder addTextBody(
125             final String name, final String text) {
126         return addTextBody(name, text, ContentType.DEFAULT_TEXT);
127     }
128 
addBinaryBody( final String name, final byte[] b, final ContentType contentType, final String filename)129     public MultipartEntityBuilder addBinaryBody(
130             final String name, final byte[] b, final ContentType contentType, final String filename) {
131         return addPart(name, new ByteArrayBody(b, contentType, filename));
132     }
133 
addBinaryBody( final String name, final byte[] b)134     public MultipartEntityBuilder addBinaryBody(
135             final String name, final byte[] b) {
136         return addBinaryBody(name, b, ContentType.DEFAULT_BINARY, null);
137     }
138 
addBinaryBody( final String name, final File file, final ContentType contentType, final String filename)139     public MultipartEntityBuilder addBinaryBody(
140             final String name, final File file, final ContentType contentType, final String filename) {
141         return addPart(name, new FileBody(file, contentType, filename));
142     }
143 
addBinaryBody( final String name, final File file)144     public MultipartEntityBuilder addBinaryBody(
145             final String name, final File file) {
146         return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
147     }
148 
addBinaryBody( final String name, final InputStream stream, final ContentType contentType, final String filename)149     public MultipartEntityBuilder addBinaryBody(
150             final String name, final InputStream stream, final ContentType contentType,
151             final String filename) {
152         return addPart(name, new InputStreamBody(stream, contentType, filename));
153     }
154 
addBinaryBody(final String name, final InputStream stream)155     public MultipartEntityBuilder addBinaryBody(final String name, final InputStream stream) {
156         return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
157     }
158 
generateContentType( final String boundary, final Charset charset)159     private String generateContentType(
160             final String boundary,
161             final Charset charset) {
162         final StringBuilder buffer = new StringBuilder();
163         buffer.append("multipart/form-data; boundary=");
164         buffer.append(boundary);
165         if (charset != null) {
166             buffer.append("; charset=");
167             buffer.append(charset.name());
168         }
169         return buffer.toString();
170     }
171 
generateBoundary()172     private String generateBoundary() {
173         final StringBuilder buffer = new StringBuilder();
174         final Random rand = new Random();
175         final int count = rand.nextInt(11) + 30; // a random size from 30 to 40
176         for (int i = 0; i < count; i++) {
177             buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
178         }
179         return buffer.toString();
180     }
181 
buildEntity()182     MultipartFormEntity buildEntity() {
183         final String st = subType != null ? subType : DEFAULT_SUBTYPE;
184         final Charset cs = charset;
185         final String b = boundary != null ? boundary : generateBoundary();
186         final List<FormBodyPart> bps = bodyParts != null ? new ArrayList<FormBodyPart>(bodyParts) :
187                 Collections.<FormBodyPart>emptyList();
188         final HttpMultipartMode m = mode != null ? mode : HttpMultipartMode.STRICT;
189         final AbstractMultipartForm form;
190         switch (m) {
191             case BROWSER_COMPATIBLE:
192                 form = new HttpBrowserCompatibleMultipart(st, cs, b, bps);
193                 break;
194             case RFC6532:
195                 form = new HttpRFC6532Multipart(st, cs, b, bps);
196                 break;
197             default:
198                 form = new HttpStrictMultipart(st, cs, b, bps);
199         }
200         return new MultipartFormEntity(form, generateContentType(b, cs), form.getTotalLength());
201     }
202 
build()203     public HttpEntity build() {
204         return buildEntity();
205     }
206 
207 }
208