View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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.After;
41  import org.junit.Assert;
42  import org.junit.Test;
43  
44  public class TestMultipartForm {
45  
46      private File tmpfile;
47  
48      @After
49      public void cleanup() {
50          if (tmpfile != null) {
51              tmpfile.delete();
52          }
53      }
54  
55      @Test
56      public 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.UTF_8))).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.<MultipartPart>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=ISO-8859-1\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=UTF-8\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=ISO-8859-1\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          Assert.assertEquals(expected, s);
93          Assert.assertEquals(s.length(), multipart.getTotalLength());
94      }
95  
96      @Test
97      public 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.<MultipartPart>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=ISO-8859-1\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         Assert.assertEquals(expected, s);
125         Assert.assertEquals(s.length(), multipart.getTotalLength());
126     }
127 
128     @Test
129     public 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.<MultipartPart>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         Assert.assertEquals(expected, s);
165         Assert.assertEquals(-1, multipart.getTotalLength());
166     }
167 
168     @Test
169     public 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.<MultipartPart>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         Assert.assertEquals(expected, s);
214         Assert.assertEquals(-1, multipart.getTotalLength());
215     }
216 
217     @Test
218     public 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.<MultipartPart>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         Assert.assertEquals(expected, s);
263         Assert.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     public 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.<MultipartPart>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         Assert.assertEquals(expected, s);
327         Assert.assertEquals(-1, multipart.getTotalLength());
328     }
329 
330     @Test
331     public 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.<MultipartPart>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         Assert.assertEquals(expected.length, actual.length);
370         for (int i = 0; i < actual.length; i++) {
371             Assert.assertEquals(expected[i], actual[i]);
372         }
373         Assert.assertEquals(expected.length, multipart.getTotalLength());
374     }
375 
376 }