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.ByteArrayOutputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FileWriter;
34 import java.io.Writer;
35 import java.nio.charset.Charset;
36 import java.nio.charset.StandardCharsets;
37 import java.util.Arrays;
38
39 import org.apache.hc.core5.http.ContentType;
40 import org.junit.jupiter.api.AfterEach;
41 import org.junit.jupiter.api.Assertions;
42 import org.junit.jupiter.api.Test;
43
44 class TestMultipartForm {
45
46 private File tmpfile;
47
48 @AfterEach
49 void cleanup() {
50 if (tmpfile != null) {
51 tmpfile.delete();
52 }
53 }
54
55 @Test
56 void testMultipartFormStringParts() throws Exception {
57 final FormBodyPart p1 = FormBodyPartBuilder.create(
58 "field1",
59 new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
60 final FormBodyPart p2 = FormBodyPartBuilder.create(
61 "field2",
62 new StringBody("that stuff", ContentType.create(
63 ContentType.TEXT_PLAIN.getMimeType(), StandardCharsets.ISO_8859_1))).build();
64 final FormBodyPart p3 = FormBodyPartBuilder.create(
65 "field3",
66 new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
67 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
68 Arrays.asList(p1, p2, p3));
69
70 final ByteArrayOutputStream out = new ByteArrayOutputStream();
71 multipart.writeTo(out);
72 out.close();
73
74 final String expected =
75 "--foo\r\n" +
76 "Content-Disposition: form-data; name=\"field1\"\r\n" +
77 "Content-Type: text/plain; charset=UTF-8\r\n" +
78 "\r\n" +
79 "this stuff\r\n" +
80 "--foo\r\n" +
81 "Content-Disposition: form-data; name=\"field2\"\r\n" +
82 "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
83 "\r\n" +
84 "that stuff\r\n" +
85 "--foo\r\n" +
86 "Content-Disposition: form-data; name=\"field3\"\r\n" +
87 "Content-Type: text/plain; charset=UTF-8\r\n" +
88 "\r\n" +
89 "all kind of stuff\r\n" +
90 "--foo--\r\n";
91 final String s = out.toString("US-ASCII");
92 Assertions.assertEquals(expected, s);
93 Assertions.assertEquals(s.length(), multipart.getTotalLength());
94 }
95
96 @Test
97 void testMultipartFormCustomContentType() throws Exception {
98 final FormBodyPart p1 = FormBodyPartBuilder.create(
99 "field1",
100 new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
101 final FormBodyPart p2 = FormBodyPartBuilder.create(
102 "field2",
103 new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
104 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
105 Arrays.asList(p1, p2));
106
107 final ByteArrayOutputStream out = new ByteArrayOutputStream();
108 multipart.writeTo(out);
109 out.close();
110
111 final String expected =
112 "--foo\r\n" +
113 "Content-Disposition: form-data; name=\"field1\"\r\n" +
114 "Content-Type: text/plain; charset=UTF-8\r\n" +
115 "\r\n" +
116 "this stuff\r\n" +
117 "--foo\r\n" +
118 "Content-Disposition: form-data; name=\"field2\"\r\n" +
119 "Content-Type: stuff/plain; param=value\r\n" +
120 "\r\n" +
121 "that stuff\r\n" +
122 "--foo--\r\n";
123 final String s = out.toString("US-ASCII");
124 Assertions.assertEquals(expected, s);
125 Assertions.assertEquals(s.length(), multipart.getTotalLength());
126 }
127
128 @Test
129 void testMultipartFormBinaryParts() throws Exception {
130 tmpfile = File.createTempFile("tmp", ".bin");
131 try (Writer writer = new FileWriter(tmpfile)) {
132 writer.append("some random whatever");
133 }
134
135 final FormBodyPart p1 = FormBodyPartBuilder.create(
136 "field1",
137 new FileBody(tmpfile)).build();
138 @SuppressWarnings("resource")
139 final FormBodyPart p2 = FormBodyPartBuilder.create(
140 "field2",
141 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
142 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
143 Arrays.asList(p1, p2));
144
145 final ByteArrayOutputStream out = new ByteArrayOutputStream();
146 multipart.writeTo(out);
147 out.close();
148
149 final String expected =
150 "--foo\r\n" +
151 "Content-Disposition: form-data; name=\"field1\"; " +
152 "filename=\"" + tmpfile.getName() + "\"\r\n" +
153 "Content-Type: application/octet-stream\r\n" +
154 "\r\n" +
155 "some random whatever\r\n" +
156 "--foo\r\n" +
157 "Content-Disposition: form-data; name=\"field2\"; " +
158 "filename=\"file.tmp\"\r\n" +
159 "Content-Type: application/octet-stream\r\n" +
160 "\r\n" +
161 "some random whatever\r\n" +
162 "--foo--\r\n";
163 final String s = out.toString("US-ASCII");
164 Assertions.assertEquals(expected, s);
165 Assertions.assertEquals(-1, multipart.getTotalLength());
166 }
167
168 @Test
169 void testMultipartFormStrict() throws Exception {
170 tmpfile = File.createTempFile("tmp", ".bin");
171 try (Writer writer = new FileWriter(tmpfile)) {
172 writer.append("some random whatever");
173 }
174
175 final FormBodyPart p1 = FormBodyPartBuilder.create(
176 "field1",
177 new FileBody(tmpfile)).build();
178 final FormBodyPart p2 = FormBodyPartBuilder.create(
179 "field2",
180 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
181 @SuppressWarnings("resource")
182 final FormBodyPart p3 = FormBodyPartBuilder.create(
183 "field3",
184 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
185 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
186 Arrays.asList(p1, p2, p3));
187
188 final ByteArrayOutputStream out = new ByteArrayOutputStream();
189 multipart.writeTo(out);
190 out.close();
191
192 final String expected =
193 "--foo\r\n" +
194 "Content-Disposition: form-data; name=\"field1\"; " +
195 "filename=\"" + tmpfile.getName() + "\"\r\n" +
196 "Content-Type: application/octet-stream\r\n" +
197 "\r\n" +
198 "some random whatever\r\n" +
199 "--foo\r\n" +
200 "Content-Disposition: form-data; name=\"field2\"; " +
201 "filename=\"test-file\"\r\n" +
202 "Content-Type: text/plain; charset=US-ASCII\r\n" +
203 "\r\n" +
204 "some random whatever\r\n" +
205 "--foo\r\n" +
206 "Content-Disposition: form-data; name=\"field3\"; " +
207 "filename=\"file.tmp\"\r\n" +
208 "Content-Type: application/octet-stream\r\n" +
209 "\r\n" +
210 "some random whatever\r\n" +
211 "--foo--\r\n";
212 final String s = out.toString("US-ASCII");
213 Assertions.assertEquals(expected, s);
214 Assertions.assertEquals(-1, multipart.getTotalLength());
215 }
216
217 @Test
218 void testMultipartFormRFC6532() throws Exception {
219 tmpfile = File.createTempFile("tmp", ".bin");
220 try (Writer writer = new FileWriter(tmpfile)) {
221 writer.append("some random whatever");
222 }
223
224 final FormBodyPart p1 = FormBodyPartBuilder.create(
225 "field1\u0414",
226 new FileBody(tmpfile)).build();
227 final FormBodyPart p2 = FormBodyPartBuilder.create(
228 "field2",
229 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
230 @SuppressWarnings("resource")
231 final FormBodyPart p3 = FormBodyPartBuilder.create(
232 "field3",
233 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
234 final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
235 Arrays.asList(p1, p2, p3));
236
237 final ByteArrayOutputStream out = new ByteArrayOutputStream();
238 multipart.writeTo(out);
239 out.close();
240
241 final String expected =
242 "--foo\r\n" +
243 "Content-Disposition: form-data; name=\"field1\u0414\"; " +
244 "filename=\"" + tmpfile.getName() + "\"\r\n" +
245 "Content-Type: application/octet-stream\r\n" +
246 "\r\n" +
247 "some random whatever\r\n" +
248 "--foo\r\n" +
249 "Content-Disposition: form-data; name=\"field2\"; " +
250 "filename=\"test-file\"\r\n" +
251 "Content-Type: text/plain; charset=US-ASCII\r\n" +
252 "\r\n" +
253 "some random whatever\r\n" +
254 "--foo\r\n" +
255 "Content-Disposition: form-data; name=\"field3\"; " +
256 "filename=\"file.tmp\"\r\n" +
257 "Content-Type: application/octet-stream\r\n" +
258 "\r\n" +
259 "some random whatever\r\n" +
260 "--foo--\r\n";
261 final String s = out.toString("UTF-8");
262 Assertions.assertEquals(expected, s);
263 Assertions.assertEquals(-1, multipart.getTotalLength());
264 }
265
266 private static final int SWISS_GERMAN_HELLO [] = {
267 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
268 };
269
270 private static final int RUSSIAN_HELLO [] = {
271 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
272 0x432, 0x435, 0x442
273 };
274
275 private static String constructString(final int [] unicodeChars) {
276 final StringBuilder buffer = new StringBuilder();
277 if (unicodeChars != null) {
278 for (final int unicodeChar : unicodeChars) {
279 buffer.append((char)unicodeChar);
280 }
281 }
282 return buffer.toString();
283 }
284
285 @Test
286 void testMultipartFormBrowserCompatibleNonASCIIHeaders() throws Exception {
287 final String s1 = constructString(SWISS_GERMAN_HELLO);
288 final String s2 = constructString(RUSSIAN_HELLO);
289
290 tmpfile = File.createTempFile("tmp", ".bin");
291 try (Writer writer = new FileWriter(tmpfile)) {
292 writer.append("some random whatever");
293 }
294
295 @SuppressWarnings("resource")
296 final FormBodyPart p1 = FormBodyPartBuilder.create(
297 "field1",
298 new InputStreamBody(new FileInputStream(tmpfile), s1 + ".tmp")).build();
299 @SuppressWarnings("resource")
300 final FormBodyPart p2 = FormBodyPartBuilder.create(
301 "field2",
302 new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
303 final LegacyMultipart multipart = new LegacyMultipart(
304 StandardCharsets.UTF_8, "foo",
305 Arrays.asList(p1, p2));
306
307 final ByteArrayOutputStream out = new ByteArrayOutputStream();
308 multipart.writeTo(out);
309 out.close();
310
311 final String expected =
312 "--foo\r\n" +
313 "Content-Disposition: form-data; name=\"field1\"; " +
314 "filename=\"" + s1 + ".tmp\"\r\n" +
315 "Content-Type: application/octet-stream\r\n" +
316 "\r\n" +
317 "some random whatever\r\n" +
318 "--foo\r\n" +
319 "Content-Disposition: form-data; name=\"field2\"; " +
320 "filename=\"" + s2 + ".tmp\"\r\n" +
321 "Content-Type: application/octet-stream\r\n" +
322 "\r\n" +
323 "some random whatever\r\n" +
324 "--foo--\r\n";
325 final String s = out.toString("UTF-8");
326 Assertions.assertEquals(expected, s);
327 Assertions.assertEquals(-1, multipart.getTotalLength());
328 }
329
330 @Test
331 void testMultipartFormStringPartsMultiCharsets() throws Exception {
332 final String s1 = constructString(SWISS_GERMAN_HELLO);
333 final String s2 = constructString(RUSSIAN_HELLO);
334
335 final FormBodyPart p1 = FormBodyPartBuilder.create(
336 "field1",
337 new StringBody(s1, ContentType.create("text/plain", StandardCharsets.ISO_8859_1))).build();
338 final FormBodyPart p2 = FormBodyPartBuilder.create(
339 "field2",
340 new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build();
341 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
342 Arrays.asList(p1, p2));
343
344 final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
345 multipart.writeTo(out1);
346 out1.close();
347
348 final ByteArrayOutputStream out2 = new ByteArrayOutputStream();
349
350 out2.write((
351 "--foo\r\n" +
352 "Content-Disposition: form-data; name=\"field1\"\r\n" +
353 "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
354 "\r\n").getBytes(StandardCharsets.US_ASCII));
355 out2.write(s1.getBytes(StandardCharsets.ISO_8859_1));
356 out2.write(("\r\n" +
357 "--foo\r\n" +
358 "Content-Disposition: form-data; name=\"field2\"\r\n" +
359 "Content-Type: text/plain; charset=KOI8-R\r\n" +
360 "\r\n").getBytes(StandardCharsets.US_ASCII));
361 out2.write(s2.getBytes(Charset.forName("KOI8-R")));
362 out2.write(("\r\n" +
363 "--foo--\r\n").getBytes(StandardCharsets.US_ASCII));
364 out2.close();
365
366 final byte[] actual = out1.toByteArray();
367 final byte[] expected = out2.toByteArray();
368
369 Assertions.assertEquals(expected.length, actual.length);
370 for (int i = 0; i < actual.length; i++) {
371 Assertions.assertEquals(expected[i], actual[i]);
372 }
373 Assertions.assertEquals(expected.length, multipart.getTotalLength());
374 }
375
376 @Test
377 void testMultipartFormBinaryPartsPreamblEpilogue() throws Exception {
378 tmpfile = File.createTempFile("tmp", ".bin");
379 try (Writer writer = new FileWriter(tmpfile)) {
380 writer.append("some random whatever");
381 }
382
383 final FormBodyPart p1 = FormBodyPartBuilder.create(
384 "field1",
385 new FileBody(tmpfile)).build();
386 @SuppressWarnings("resource")
387 final FormBodyPart p2 = FormBodyPartBuilder.create(
388 "field2",
389 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
390 final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
391 Arrays.asList(p1, p2), "This is the preamble", "This is the epilogue");
392
393 final ByteArrayOutputStream out = new ByteArrayOutputStream();
394 multipart.writeTo(out);
395 out.close();
396
397 final String expected =
398 "This is the preamble\r\n" +
399 "--foo\r\n" +
400 "Content-Disposition: form-data; name=\"field1\"; " +
401 "filename=\"" + tmpfile.getName() + "\"\r\n" +
402 "Content-Type: application/octet-stream\r\n" +
403 "\r\n" +
404 "some random whatever\r\n" +
405 "--foo\r\n" +
406 "Content-Disposition: form-data; name=\"field2\"; " +
407 "filename=\"file.tmp\"\r\n" +
408 "Content-Type: application/octet-stream\r\n" +
409 "\r\n" +
410 "some random whatever\r\n" +
411 "--foo--\r\n" +
412 "This is the epilogue\r\n";
413 final String s = out.toString("US-ASCII");
414 Assertions.assertEquals(expected, s);
415 Assertions.assertEquals(-1, multipart.getTotalLength());
416 }
417 }