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