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 private static final NameValuePair[] EMPTY_NAME_VALUE_ARRAY = {};
70
71 public static MultipartEntityBuilder create() {
72 return new MultipartEntityBuilder();
73 }
74
75 MultipartEntityBuilder() {
76 }
77
78 public MultipartEntityBuilder setMode(final HttpMultipartMode mode) {
79 this.mode = mode;
80 return this;
81 }
82
83 public MultipartEntityBuilder setLaxMode() {
84 this.mode = HttpMultipartMode.LEGACY;
85 return this;
86 }
87
88 public MultipartEntityBuilder setStrictMode() {
89 this.mode = HttpMultipartMode.STRICT;
90 return this;
91 }
92
93 public MultipartEntityBuilder setBoundary(final String boundary) {
94 this.boundary = boundary;
95 return this;
96 }
97
98
99
100
101 public MultipartEntityBuilder setMimeSubtype(final String subType) {
102 Args.notBlank(subType, "MIME subtype");
103 this.contentType = ContentType.create("multipart/" + subType);
104 return this;
105 }
106
107
108
109
110 public MultipartEntityBuilder setContentType(final ContentType contentType) {
111 Args.notNull(contentType, "Content type");
112 this.contentType = contentType;
113 return this;
114 }
115
116
117
118
119
120
121
122 public MultipartEntityBuilder addParameter(final BasicNameValuePair parameter) {
123 this.contentType = contentType.withParameters(parameter);
124 return this;
125 }
126
127 public MultipartEntityBuilder setCharset(final Charset charset) {
128 this.charset = charset;
129 return this;
130 }
131
132
133
134
135 public MultipartEntityBuilder addPart(final MultipartPart multipartPart) {
136 if (multipartPart == null) {
137 return this;
138 }
139 if (this.multipartParts == null) {
140 this.multipartParts = new ArrayList<>();
141 }
142 this.multipartParts.add(multipartPart);
143 return this;
144 }
145
146 public MultipartEntityBuilder addPart(final String name, final ContentBody contentBody) {
147 Args.notNull(name, "Name");
148 Args.notNull(contentBody, "Content body");
149 return addPart(FormBodyPartBuilder.create(name, contentBody).build());
150 }
151
152 public MultipartEntityBuilder addTextBody(
153 final String name, final String text, final ContentType contentType) {
154 return addPart(name, new StringBody(text, contentType));
155 }
156
157 public MultipartEntityBuilder addTextBody(
158 final String name, final String text) {
159 return addTextBody(name, text, ContentType.DEFAULT_TEXT);
160 }
161
162 public MultipartEntityBuilder addBinaryBody(
163 final String name, final byte[] b, final ContentType contentType, final String filename) {
164 return addPart(name, new ByteArrayBody(b, contentType, filename));
165 }
166
167 public MultipartEntityBuilder addBinaryBody(
168 final String name, final byte[] b) {
169 return addPart(name, new ByteArrayBody(b, ContentType.DEFAULT_BINARY));
170 }
171
172 public MultipartEntityBuilder addBinaryBody(
173 final String name, final File file, final ContentType contentType, final String filename) {
174 return addPart(name, new FileBody(file, contentType, filename));
175 }
176
177 public MultipartEntityBuilder addBinaryBody(
178 final String name, final File file) {
179 return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
180 }
181
182 public MultipartEntityBuilder addBinaryBody(
183 final String name, final InputStream stream, final ContentType contentType,
184 final String filename) {
185 return addPart(name, new InputStreamBody(stream, contentType, filename));
186 }
187
188 public MultipartEntityBuilder addBinaryBody(final String name, final InputStream stream) {
189 return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
190 }
191
192 private String generateBoundary() {
193 final ThreadLocalRandom rand = ThreadLocalRandom.current();
194 final int count = rand.nextInt(30, 41);
195 final CharBuffer buffer = CharBuffer.allocate(count);
196 while (buffer.hasRemaining()) {
197 buffer.put(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
198 }
199 buffer.flip();
200 return buffer.toString();
201 }
202
203 MultipartFormEntity buildEntity() {
204 String boundaryCopy = boundary;
205 if (boundaryCopy == null && contentType != null) {
206 boundaryCopy = contentType.getParameter("boundary");
207 }
208 if (boundaryCopy == null) {
209 boundaryCopy = generateBoundary();
210 }
211 Charset charsetCopy = charset;
212 if (charsetCopy == null && contentType != null) {
213 charsetCopy = contentType.getCharset();
214 }
215 final List<NameValuePair> paramsList = new ArrayList<>(2);
216 paramsList.add(new BasicNameValuePair("boundary", boundaryCopy));
217 if (charsetCopy != null) {
218 paramsList.add(new BasicNameValuePair("charset", charsetCopy.name()));
219 }
220 final NameValuePair[] params = paramsList.toArray(EMPTY_NAME_VALUE_ARRAY);
221
222 final ContentType contentTypeCopy;
223 if (contentType != null) {
224 contentTypeCopy = contentType.withParameters(params);
225 } else {
226 boolean formData = false;
227 if (multipartParts != null) {
228 for (final MultipartPart multipartPart : multipartParts) {
229 if (multipartPart instanceof FormBodyPart) {
230 formData = true;
231 break;
232 }
233 }
234 }
235
236 if (formData) {
237 contentTypeCopy = ContentType.MULTIPART_FORM_DATA.withParameters(params);
238 } else {
239 contentTypeCopy = ContentType.create("multipart/mixed", params);
240 }
241 }
242 final List<MultipartPart> multipartPartsCopy = multipartParts != null ? new ArrayList<>(multipartParts) :
243 Collections.emptyList();
244 final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT;
245 final AbstractMultipartFormat form;
246 switch (modeCopy) {
247 case LEGACY:
248 form = new LegacyMultipart(charsetCopy, boundaryCopy, multipartPartsCopy);
249 break;
250 case EXTENDED:
251 if (contentTypeCopy.isSameMimeType(ContentType.MULTIPART_FORM_DATA)) {
252 if (charsetCopy == null) {
253 charsetCopy = StandardCharsets.UTF_8;
254 }
255 form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
256 } else {
257 form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
258 }
259 break;
260 default:
261 form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, multipartPartsCopy);
262 }
263 return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
264 }
265
266 public HttpEntity build() {
267 return buildEntity();
268 }
269
270 }