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.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.File;
33 import java.nio.charset.StandardCharsets;
34 import java.util.ArrayList;
35 import java.util.List;
36
37 import org.apache.hc.core5.http.ContentType;
38 import org.apache.hc.core5.http.NameValuePair;
39 import org.apache.hc.core5.http.message.BasicNameValuePair;
40 import org.junit.jupiter.api.Assertions;
41 import org.junit.jupiter.api.Test;
42
43 class TestMultipartEntityBuilder {
44
45 @Test
46 void testBasics() {
47 final MultipartFormEntity entity = MultipartEntityBuilder.create().buildEntity();
48 Assertions.assertNotNull(entity);
49 Assertions.assertTrue(entity.getMultipart() instanceof HttpStrictMultipart);
50 Assertions.assertEquals(0, entity.getMultipart().getParts().size());
51 }
52
53 @Test
54 void testMultipartOptions() {
55 final MultipartFormEntity entity = MultipartEntityBuilder.create()
56 .setBoundary("blah-blah")
57 .setCharset(StandardCharsets.UTF_8)
58 .setLaxMode()
59 .buildEntity();
60 Assertions.assertNotNull(entity);
61 Assertions.assertTrue(entity.getMultipart() instanceof LegacyMultipart);
62 Assertions.assertEquals("blah-blah", entity.getMultipart().boundary);
63 Assertions.assertEquals(StandardCharsets.UTF_8, entity.getMultipart().charset);
64 }
65
66 @Test
67 void testAddBodyParts() {
68 final MultipartFormEntity entity = MultipartEntityBuilder.create()
69 .addTextBody("p1", "stuff")
70 .addBinaryBody("p2", new File("stuff"))
71 .addBinaryBody("p3", new byte[]{})
72 .addBinaryBody("p4", new ByteArrayInputStream(new byte[]{}))
73 .addBinaryBody("p5", new ByteArrayInputStream(new byte[]{}), ContentType.DEFAULT_BINARY, "filename")
74 .buildEntity();
75 Assertions.assertNotNull(entity);
76 final List<MultipartPart> bodyParts = entity.getMultipart().getParts();
77 Assertions.assertNotNull(bodyParts);
78 Assertions.assertEquals(5, bodyParts.size());
79 }
80
81
82 @Test
83 void testMultipartCustomContentType() {
84 final MultipartFormEntity entity = MultipartEntityBuilder.create()
85 .setContentType(ContentType.APPLICATION_XML)
86 .setBoundary("blah-blah")
87 .setCharset(StandardCharsets.UTF_8)
88 .setLaxMode()
89 .buildEntity();
90 Assertions.assertNotNull(entity);
91 Assertions.assertEquals("application/xml; charset=UTF-8; boundary=blah-blah", entity.getContentType());
92 }
93
94 @Test
95 void testMultipartContentTypeParameter() {
96 final MultipartFormEntity entity = MultipartEntityBuilder.create()
97 .setContentType(ContentType.MULTIPART_FORM_DATA.withParameters(
98 new BasicNameValuePair("boundary", "yada-yada"),
99 new BasicNameValuePair("charset", "ascii")))
100 .buildEntity();
101 Assertions.assertNotNull(entity);
102 Assertions.assertEquals("multipart/form-data; boundary=yada-yada; charset=ascii", entity.getContentType());
103 Assertions.assertEquals("yada-yada", entity.getMultipart().boundary);
104 Assertions.assertEquals(StandardCharsets.US_ASCII, entity.getMultipart().charset);
105 }
106
107 @Test
108 void testMultipartDefaultContentTypeOmitsCharset() {
109 final MultipartFormEntity entity = MultipartEntityBuilder.create()
110 .setCharset(StandardCharsets.UTF_8)
111 .setBoundary("yada-yada")
112 .buildEntity();
113 Assertions.assertNotNull(entity);
114 Assertions.assertEquals("multipart/mixed; boundary=yada-yada", entity.getContentType());
115 Assertions.assertEquals("yada-yada", entity.getMultipart().boundary);
116 }
117
118 @Test
119 void testMultipartFormDataContentTypeOmitsCharset() {
120
121
122 final MultipartFormEntity entity = MultipartEntityBuilder.create()
123 .setContentType(ContentType.create("multipart/form-data"))
124 .setCharset(StandardCharsets.UTF_8)
125 .setBoundary("yada-yada")
126 .buildEntity();
127 Assertions.assertNotNull(entity);
128 Assertions.assertEquals("multipart/form-data; boundary=yada-yada", entity.getContentType());
129 Assertions.assertEquals("yada-yada", entity.getMultipart().boundary);
130 }
131
132 @Test
133 void testMultipartCustomContentTypeParameterOverrides() {
134 final MultipartFormEntity entity = MultipartEntityBuilder.create()
135 .setContentType(ContentType.MULTIPART_FORM_DATA.withParameters(
136 new BasicNameValuePair("boundary", "yada-yada"),
137 new BasicNameValuePair("charset", "ascii"),
138 new BasicNameValuePair("my", "stuff")))
139 .setBoundary("blah-blah")
140 .setCharset(StandardCharsets.UTF_8)
141 .setLaxMode()
142 .buildEntity();
143 Assertions.assertNotNull(entity);
144 Assertions.assertEquals("multipart/form-data; boundary=blah-blah; charset=ascii; my=stuff",
145 entity.getContentType());
146 }
147
148 @Test
149 void testMultipartCustomContentTypeUsingAddParameter() {
150 final MultipartEntityBuilder eb = MultipartEntityBuilder.create();
151 eb.setMimeSubtype("related");
152 eb.addParameter(new BasicNameValuePair("boundary", "yada-yada"));
153 eb.addParameter(new BasicNameValuePair("charset", "ascii"));
154 eb.addParameter(new BasicNameValuePair("my", "stuff"));
155 eb.buildEntity();
156 final MultipartFormEntity entity = eb.buildEntity();
157 Assertions.assertNotNull(entity);
158 Assertions.assertEquals("multipart/related; boundary=yada-yada; charset=ascii; my=stuff",
159 entity.getContentType());
160 }
161
162 @Test
163 void testMultipartWriteTo() throws Exception {
164 final String helloWorld = "hello world";
165 final List<NameValuePair> parameters = new ArrayList<>();
166 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_NAME, "test"));
167 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_FILENAME, helloWorld));
168 final MultipartFormEntity entity = MultipartEntityBuilder.create()
169 .setStrictMode()
170 .setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
171 .addPart(new FormBodyPartBuilder()
172 .setName("test")
173 .setBody(new StringBody("hello world", ContentType.TEXT_PLAIN))
174 .addField("Content-Disposition", "multipart/form-data", parameters)
175 .build())
176 .buildEntity();
177
178
179 final ByteArrayOutputStream out = new ByteArrayOutputStream();
180 entity.writeTo(out);
181 out.close();
182 Assertions.assertEquals("--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
183 "Content-Disposition: multipart/form-data; name=\"test\"; filename=\"hello world\"\r\n" +
184 "Content-Type: text/plain; charset=UTF-8\r\n" +
185 "\r\n" +
186 helloWorld + "\r\n" +
187 "--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n", out.toString(StandardCharsets.US_ASCII.name()));
188 }
189
190 @Test
191 void testMultipartWriteToRFC7578Mode() throws Exception {
192 final String helloWorld = "hello \u03BA\u03CC\u03C3\u03BC\u03B5!%";
193 final List<NameValuePair> parameters = new ArrayList<>();
194 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_NAME, "test"));
195 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_FILENAME, helloWorld));
196
197 final MultipartFormEntity entity = MultipartEntityBuilder.create()
198 .setMode(HttpMultipartMode.EXTENDED)
199 .setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
200 .addPart(new FormBodyPartBuilder()
201 .setName("test")
202 .setBody(new StringBody(helloWorld, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)))
203 .addField("Content-Disposition", "multipart/form-data", parameters)
204 .build())
205 .buildEntity();
206
207 final ByteArrayOutputStream out = new ByteArrayOutputStream();
208 entity.writeTo(out);
209 out.close();
210 Assertions.assertEquals("--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
211 "Content-Disposition: multipart/form-data; name=\"test\"; filename=\"hello%20%CE%BA%CF%8C%CF%83%CE%BC%CE%B5!%25\"\r\n" +
212 "Content-Type: text/plain; charset=UTF-8\r\n" +
213 "\r\n" +
214 "hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\r\n" +
215 "--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n", out.toString(StandardCharsets.ISO_8859_1.name()));
216 }
217
218 @Test
219 void testMultipartWriteToRFC6532Mode() throws Exception {
220 final String helloWorld = "hello \u03BA\u03CC\u03C3\u03BC\u03B5!%";
221 final List<NameValuePair> parameters = new ArrayList<>();
222 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_NAME, "test"));
223 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_FILENAME, helloWorld));
224
225 final MultipartFormEntity entity = MultipartEntityBuilder.create()
226 .setMode(HttpMultipartMode.EXTENDED)
227 .setContentType(ContentType.create("multipart/other"))
228 .setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
229 .addPart(new FormBodyPartBuilder()
230 .setName("test")
231 .setBody(new StringBody(helloWorld, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)))
232 .addField("Content-Disposition", "multipart/form-data", parameters)
233 .build())
234 .buildEntity();
235
236 final ByteArrayOutputStream out = new ByteArrayOutputStream();
237 entity.writeTo(out);
238 out.close();
239 Assertions.assertEquals("--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
240 "Content-Disposition: multipart/form-data; name=\"test\"; " +
241 "filename=\"hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\"\r\n" +
242 "Content-Type: text/plain; charset=UTF-8\r\n" +
243 "\r\n" +
244 "hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\r\n" +
245 "--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n", out.toString(StandardCharsets.ISO_8859_1.name()));
246 }
247
248 @Test
249 void testMultipartWriteToWithPreambleAndEpilogue() throws Exception {
250 final String helloWorld = "hello \u03BA\u03CC\u03C3\u03BC\u03B5!%";
251 final List<NameValuePair> parameters = new ArrayList<>();
252 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_NAME, "test"));
253 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_FILENAME, helloWorld));
254
255 final MultipartFormEntity entity = MultipartEntityBuilder.create()
256 .setMode(HttpMultipartMode.EXTENDED)
257 .setContentType(ContentType.create("multipart/other"))
258 .setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
259 .addPart(new FormBodyPartBuilder()
260 .setName("test")
261 .setBody(new StringBody(helloWorld, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)))
262 .addField("Content-Disposition", "multipart/form-data", parameters)
263 .build())
264 .addPreamble("This is the preamble.")
265 .addEpilogue("This is the epilogue.")
266 .buildEntity();
267
268 final ByteArrayOutputStream out = new ByteArrayOutputStream();
269 entity.writeTo(out);
270 out.close();
271 Assertions.assertEquals("This is the preamble.\r\n" +
272 "--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
273 "Content-Disposition: multipart/form-data; name=\"test\"; " +
274 "filename=\"hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\"\r\n" +
275 "Content-Type: text/plain; charset=UTF-8\r\n" +
276 "\r\n" +
277 "hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\r\n" +
278 "--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n" +
279 "This is the epilogue.\r\n", out.toString(StandardCharsets.ISO_8859_1.name()));
280 }
281
282 @Test
283 void testMultipartWriteToRFC7578ModeWithFilenameStar() throws Exception {
284 final String helloWorld = "hello \u03BA\u03CC\u03C3\u03BC\u03B5!%";
285 final List<NameValuePair> parameters = new ArrayList<>();
286 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_NAME, "test"));
287 parameters.add(new BasicNameValuePair(MimeConsts.FIELD_PARAM_FILENAME_START, helloWorld));
288
289 final MultipartFormEntity entity = MultipartEntityBuilder.create()
290 .setMode(HttpMultipartMode.EXTENDED)
291 .setBoundary("xxxxxxxxxxxxxxxxxxxxxxxx")
292 .addPart(new FormBodyPartBuilder()
293 .setName("test")
294 .setBody(new StringBody(helloWorld, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)))
295 .addField("Content-Disposition", "multipart/form-data", parameters)
296 .build())
297 .buildEntity();
298
299 final ByteArrayOutputStream out = new ByteArrayOutputStream();
300 entity.writeTo(out);
301 out.close();
302 Assertions.assertEquals("--xxxxxxxxxxxxxxxxxxxxxxxx\r\n" +
303 "Content-Disposition: multipart/form-data; name=\"test\"; filename*=\"UTF-8''hello%20%CE%BA%CF%8C%CF%83%CE%BC%CE%B5!%25\"\r\n" +
304 "Content-Type: text/plain; charset=UTF-8\r\n" +
305 "\r\n" +
306 "hello \u00ce\u00ba\u00cf\u008c\u00cf\u0083\u00ce\u00bc\u00ce\u00b5!%\r\n" +
307 "--xxxxxxxxxxxxxxxxxxxxxxxx--\r\n", out.toString(StandardCharsets.ISO_8859_1.name()));
308 }
309
310 }