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 = new NameValuePair[0];
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 public MultipartEntityBuilder setCharset(final Charset charset) {
117 this.charset = charset;
118 return this;
119 }
120
121
122
123
124 public MultipartEntityBuilder addPart(final MultipartPart multipartPart) {
125 if (multipartPart == null) {
126 return this;
127 }
128 if (this.multipartParts == null) {
129 this.multipartParts = new ArrayList<>();
130 }
131 this.multipartParts.add(multipartPart);
132 return this;
133 }
134
135 public MultipartEntityBuilder addPart(final String name, final ContentBody contentBody) {
136 Args.notNull(name, "Name");
137 Args.notNull(contentBody, "Content body");
138 return addPart(FormBodyPartBuilder.create(name, contentBody).build());
139 }
140
141 public MultipartEntityBuilder addTextBody(
142 final String name, final String text, final ContentType contentType) {
143 return addPart(name, new StringBody(text, contentType));
144 }
145
146 public MultipartEntityBuilder addTextBody(
147 final String name, final String text) {
148 return addTextBody(name, text, ContentType.DEFAULT_TEXT);
149 }
150
151 public MultipartEntityBuilder addBinaryBody(
152 final String name, final byte[] b, final ContentType contentType, final String filename) {
153 return addPart(name, new ByteArrayBody(b, contentType, filename));
154 }
155
156 public MultipartEntityBuilder addBinaryBody(
157 final String name, final byte[] b) {
158 return addBinaryBody(name, b, ContentType.DEFAULT_BINARY, null);
159 }
160
161 public MultipartEntityBuilder addBinaryBody(
162 final String name, final File file, final ContentType contentType, final String filename) {
163 return addPart(name, new FileBody(file, contentType, filename));
164 }
165
166 public MultipartEntityBuilder addBinaryBody(
167 final String name, final File file) {
168 return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
169 }
170
171 public MultipartEntityBuilder addBinaryBody(
172 final String name, final InputStream stream, final ContentType contentType,
173 final String filename) {
174 return addPart(name, new InputStreamBody(stream, contentType, filename));
175 }
176
177 public MultipartEntityBuilder addBinaryBody(final String name, final InputStream stream) {
178 return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
179 }
180
181 private String generateBoundary() {
182 final ThreadLocalRandom rand = ThreadLocalRandom.current();
183 final int count = rand.nextInt(30, 41);
184 final CharBuffer buffer = CharBuffer.allocate(count);
185 while (buffer.hasRemaining()) {
186 buffer.put(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
187 }
188 buffer.flip();
189 return buffer.toString();
190 }
191
192 MultipartFormEntity buildEntity() {
193 String boundaryCopy = boundary;
194 if (boundaryCopy == null && contentType != null) {
195 boundaryCopy = contentType.getParameter("boundary");
196 }
197 if (boundaryCopy == null) {
198 boundaryCopy = generateBoundary();
199 }
200 Charset charsetCopy = charset;
201 if (charsetCopy == null && contentType != null) {
202 charsetCopy = contentType.getCharset();
203 }
204 final List<NameValuePair> paramsList = new ArrayList<>(2);
205 paramsList.add(new BasicNameValuePair("boundary", boundaryCopy));
206 if (charsetCopy != null) {
207 paramsList.add(new BasicNameValuePair("charset", charsetCopy.name()));
208 }
209 final NameValuePair[] params = paramsList.toArray(EMPTY_NAME_VALUE_ARRAY);
210
211 final ContentType contentTypeCopy;
212 if (contentType != null) {
213 contentTypeCopy = contentType.withParameters(params);
214 } else {
215 boolean formData = false;
216 if (multipartParts != null) {
217 for (final MultipartPart multipartPart : multipartParts) {
218 if (multipartPart instanceof FormBodyPart) {
219 formData = true;
220 break;
221 }
222 }
223 }
224
225 if (formData) {
226 contentTypeCopy = ContentType.MULTIPART_FORM_DATA.withParameters(params);
227 } else {
228 contentTypeCopy = ContentType.create("multipart/mixed", params);
229 }
230 }
231 final List<MultipartPart> multipartPartsCopy = multipartParts != null ? new ArrayList<>(multipartParts) :
232 Collections.<MultipartPart>emptyList();
233 final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT;
234 final AbstractMultipartFormat form;
235 switch (modeCopy) {
236 case LEGACY:
237 form = new LegacyMultipart(charsetCopy, boundaryCopy, multipartPartsCopy);
238 break;
239 case EXTENDED:
240 if (contentTypeCopy.isSameMimeType(ContentType.MULTIPART_FORM_DATA)) {
241 if (charsetCopy == null) {
242 charsetCopy = StandardCharsets.UTF_8;
243 }
244 form = new HttpRFC7578Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
245 } else {
246 form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, multipartPartsCopy);
247 }
248 break;
249 default:
250 form = new HttpStrictMultipart(StandardCharsets.US_ASCII, boundaryCopy, multipartPartsCopy);
251 }
252 return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
253 }
254
255 public HttpEntity build() {
256 return buildEntity();
257 }
258
259 }