1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.entity.mime;
29
30 import java.io.File;
31 import java.io.InputStream;
32 import java.nio.CharBuffer;
33 import java.nio.charset.Charset;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.concurrent.ThreadLocalRandom;
39
40 import org.apache.hc.core5.http.ContentType;
41 import org.apache.hc.core5.http.HttpEntity;
42 import org.apache.hc.core5.http.NameValuePair;
43 import org.apache.hc.core5.http.message.BasicNameValuePair;
44 import org.apache.hc.core5.util.Args;
45
46
47
48
49
50
51 public class MultipartEntityBuilder {
52
53
54
55
56 private final static char[] MULTIPART_CHARS =
57 "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
58 .toCharArray();
59
60 private ContentType contentType;
61 private HttpMultipartMode mode = HttpMultipartMode.STRICT;
62 private String boundary;
63 private Charset charset;
64 private List<MultipartPart> multipartParts;
65
66
67
68
69
70
71 private String preamble;
72
73
74
75
76
77
78 private String epilogue;
79
80
81
82
83 private static final NameValuePair[] EMPTY_NAME_VALUE_ARRAY = {};
84
85 public static MultipartEntityBuilder create() {
86 return new MultipartEntityBuilder();
87 }
88
89 MultipartEntityBuilder() {
90 }
91
92 public MultipartEntityBuilder setMode(final HttpMultipartMode mode) {
93 this.mode = mode;
94 return this;
95 }
96
97 public MultipartEntityBuilder setLaxMode() {
98 this.mode = HttpMultipartMode.LEGACY;
99 return this;
100 }
101
102 public MultipartEntityBuilder setStrictMode() {
103 this.mode = HttpMultipartMode.STRICT;
104 return this;
105 }
106
107 public MultipartEntityBuilder setBoundary(final String boundary) {
108 this.boundary = boundary;
109 return this;
110 }
111
112
113
114
115 public MultipartEntityBuilder setMimeSubtype(final String subType) {
116 Args.notBlank(subType, "MIME subtype");
117 this.contentType = ContentType.create("multipart/" + subType);
118 return this;
119 }
120
121
122
123
124 public MultipartEntityBuilder setContentType(final ContentType contentType) {
125 Args.notNull(contentType, "Content type");
126 this.contentType = contentType;
127 return this;
128 }
129
130
131
132
133
134
135
136 public MultipartEntityBuilder addParameter(final BasicNameValuePair parameter) {
137 this.contentType = contentType.withParameters(parameter);
138 return this;
139 }
140
141 public MultipartEntityBuilder setCharset(final Charset charset) {
142 this.charset = charset;
143 return this;
144 }
145
146
147
148
149 public MultipartEntityBuilder addPart(final MultipartPart multipartPart) {
150 if (multipartPart == null) {
151 return this;
152 }
153 if (this.multipartParts == null) {
154 this.multipartParts = new ArrayList<>();
155 }
156 this.multipartParts.add(multipartPart);
157 return this;
158 }
159
160 public MultipartEntityBuilder addPart(final String name, final ContentBody contentBody) {
161 Args.notNull(name, "Name");
162 Args.notNull(contentBody, "Content body");
163 return addPart(FormBodyPartBuilder.create(name, contentBody).build());
164 }
165
166 public MultipartEntityBuilder addTextBody(
167 final String name, final String text, final ContentType contentType) {
168 return addPart(name, new StringBody(text, contentType));
169 }
170
171 public MultipartEntityBuilder addTextBody(
172 final String name, final String text) {
173 return addTextBody(name, text, ContentType.DEFAULT_TEXT);
174 }
175
176 public MultipartEntityBuilder addBinaryBody(
177 final String name, final byte[] b, final ContentType contentType, final String filename) {
178 return addPart(name, new ByteArrayBody(b, contentType, filename));
179 }
180
181 public MultipartEntityBuilder addBinaryBody(
182 final String name, final byte[] b) {
183 return addPart(name, new ByteArrayBody(b, ContentType.DEFAULT_BINARY));
184 }
185
186 public MultipartEntityBuilder addBinaryBody(
187 final String name, final File file, final ContentType contentType, final String filename) {
188 return addPart(name, new FileBody(file, contentType, filename));
189 }
190
191 public MultipartEntityBuilder addBinaryBody(
192 final String name, final File file) {
193 return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
194 }
195
196 public MultipartEntityBuilder addBinaryBody(
197 final String name, final InputStream stream, final ContentType contentType,
198 final String filename) {
199 return addPart(name, new InputStreamBody(stream, contentType, filename));
200 }
201
202 public MultipartEntityBuilder addBinaryBody(final String name, final InputStream stream) {
203 return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
204 }
205
206
207
208
209
210
211
212
213
214
215 public MultipartEntityBuilder addPreamble(final String preamble) {
216 this.preamble = preamble;
217 return this;
218 }
219
220
221
222
223
224
225
226
227
228 public MultipartEntityBuilder addEpilogue(final String epilogue) {
229 this.epilogue = epilogue;
230 return this;
231 }
232
233 private String generateBoundary() {
234 final ThreadLocalRandom rand = ThreadLocalRandom.current();
235 final int count = rand.nextInt(30, 41);
236 final CharBuffer buffer = CharBuffer.allocate(count);
237 while (buffer.hasRemaining()) {
238 buffer.put(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
239 }
240 buffer.flip();
241 return buffer.toString();
242 }
243
244 MultipartFormEntity buildEntity() {
245 String boundaryCopy = boundary;
246 if (boundaryCopy == null && contentType != null) {
247 boundaryCopy = contentType.getParameter("boundary");
248 }
249 if (boundaryCopy == null) {
250 boundaryCopy = generateBoundary();
251 }
252 Charset charsetCopy = charset;
253 if (charsetCopy == null && contentType != null) {
254 charsetCopy = contentType.getCharset();
255 }
256 final List<NameValuePair> paramsList = new ArrayList<>(2);
257 paramsList.add(new BasicNameValuePair("boundary", boundaryCopy));
258 if (charsetCopy != null) {
259 paramsList.add(new BasicNameValuePair("charset", charsetCopy.name()));
260 }
261 final NameValuePair[] params = paramsList.toArray(EMPTY_NAME_VALUE_ARRAY);
262
263 final ContentType contentTypeCopy;
264 if (contentType != null) {
265 contentTypeCopy = contentType.withParameters(params);
266 } else {
267 boolean formData = false;
268 if (multipartParts != null) {
269 for (final MultipartPart multipartPart : multipartParts) {
270 if (multipartPart instanceof FormBodyPart) {
271 formData = true;
272 break;
273 }
274 }
275 }
276
277 if (formData) {
278 contentTypeCopy = ContentType.MULTIPART_FORM_DATA.withParameters(params);
279 } else {
280 contentTypeCopy = ContentType.create("multipart/mixed", params);
281 }
282 }
283 final List<MultipartPart> multipartPartsCopy = multipartParts != null ? new ArrayList<>(multipartParts) :
284 Collections.emptyList();
285 final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT;
286 final AbstractMultipartFormat form;
287 switch (modeCopy) {
288 case LEGACY:
289 form = new LegacyMultipart(charsetCopy, boundaryCopy, multipartPartsCopy);
290 break;
291 case EXTENDED:
292 if (contentTypeCopy.isSameMimeType(ContentType.MULTIPART_FORM_DATA)) {
293 if (charsetCopy == null) {
294 charsetCopy = StandardCharsets.UTF_8;
295 }
296 form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, multipartPartsCopy, preamble, epilogue);
297 } else {
298 form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, multipartPartsCopy, preamble, epilogue);
299 }
300 break;
301 default:
302 form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, multipartPartsCopy, preamble, epilogue);
303 }
304 return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
305 }
306
307 public HttpEntity build() {
308 return buildEntity();
309 }
310
311 }