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.jupiter.api.AfterEach;
41  import org.junit.jupiter.api.Assertions;
42  import org.junit.jupiter.api.Test;
43  
44  public class TestMultipartMixed {
45  
46      private File tmpfile;
47  
48      @AfterEach
49      public void cleanup() {
50          if (tmpfile != null) {
51              tmpfile.delete();
52          }
53      }
54  
55      @Test
56      public void testMultipartPartStringParts() throws Exception {
57          final MultipartPart p1 = MultipartPartBuilder.create(
58                  new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
59          final MultipartPart p2 = MultipartPartBuilder.create(
60                  new StringBody("that stuff", ContentType.create(
61                          ContentType.TEXT_PLAIN.getMimeType(), StandardCharsets.UTF_8))).build();
62          final MultipartPart p3 = MultipartPartBuilder.create(
63                  new StringBody("all kind of stuff", ContentType.DEFAULT_TEXT)).build();
64          final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
65                  Arrays.asList(p1, p2, p3));
66  
67          final ByteArrayOutputStream out = new ByteArrayOutputStream();
68          multipart.writeTo(out);
69          out.close();
70  
71          final String expected =
72              "--foo\r\n" +
73              "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
74              "\r\n" +
75              "this stuff\r\n" +
76              "--foo\r\n" +
77              "Content-Type: text/plain; charset=UTF-8\r\n" +
78              "\r\n" +
79              "that stuff\r\n" +
80              "--foo\r\n" +
81              "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
82              "\r\n" +
83              "all kind of stuff\r\n" +
84              "--foo--\r\n";
85          final String s = out.toString("US-ASCII");
86          Assertions.assertEquals(expected, s);
87          Assertions.assertEquals(s.length(), multipart.getTotalLength());
88      }
89  
90      @Test
91      public void testMultipartPartCustomContentType() throws Exception {
92          final MultipartPart p1 = MultipartPartBuilder.create(
93                  new StringBody("this stuff", ContentType.DEFAULT_TEXT)).build();
94          final MultipartPart p2 = MultipartPartBuilder.create(
95                  new StringBody("that stuff", ContentType.parse("stuff/plain; param=value"))).build();
96          final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
97                  Arrays.asList(p1, p2));
98  
99          final ByteArrayOutputStream out = new ByteArrayOutputStream();
100         multipart.writeTo(out);
101         out.close();
102 
103         final String expected =
104                 "--foo\r\n" +
105                         "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
106                         "\r\n" +
107                         "this stuff\r\n" +
108                         "--foo\r\n" +
109                         "Content-Type: stuff/plain; param=value\r\n" +
110                         "\r\n" +
111                         "that stuff\r\n" +
112                         "--foo--\r\n";
113         final String s = out.toString("US-ASCII");
114         Assertions.assertEquals(expected, s);
115         Assertions.assertEquals(s.length(), multipart.getTotalLength());
116     }
117 
118     @Test
119     public void testMultipartPartBinaryParts() throws Exception {
120         tmpfile = File.createTempFile("tmp", ".bin");
121         try (Writer writer = new FileWriter(tmpfile)) {
122             writer.append("some random whatever");
123         }
124 
125         final MultipartPart p1 = MultipartPartBuilder.create(
126                 new FileBody(tmpfile)).build();
127         @SuppressWarnings("resource")
128         final MultipartPart p2 = MultipartPartBuilder.create(
129                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
130         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
131                 Arrays.asList(p1, p2));
132 
133         final ByteArrayOutputStream out = new ByteArrayOutputStream();
134         multipart.writeTo(out);
135         out.close();
136 
137         final String expected =
138             "--foo\r\n" +
139             "Content-Type: application/octet-stream\r\n" +
140             "\r\n" +
141             "some random whatever\r\n" +
142             "--foo\r\n" +
143             "Content-Type: application/octet-stream\r\n" +
144             "\r\n" +
145             "some random whatever\r\n" +
146             "--foo--\r\n";
147         final String s = out.toString("US-ASCII");
148         Assertions.assertEquals(expected, s);
149         Assertions.assertEquals(-1, multipart.getTotalLength());
150     }
151 
152     @Test
153     public void testMultipartPartStrict() throws Exception {
154         tmpfile = File.createTempFile("tmp", ".bin");
155         try (Writer writer = new FileWriter(tmpfile)) {
156             writer.append("some random whatever");
157         }
158 
159         final MultipartPart p1 = MultipartPartBuilder.create(
160                 new FileBody(tmpfile)).build();
161         final MultipartPart p2 = MultipartPartBuilder.create(
162                 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
163         @SuppressWarnings("resource")
164         final MultipartPart p3 = MultipartPartBuilder.create(
165                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
166         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
167                 Arrays.asList(p1, p2, p3));
168 
169         final ByteArrayOutputStream out = new ByteArrayOutputStream();
170         multipart.writeTo(out);
171         out.close();
172 
173         final String expected =
174             "--foo\r\n" +
175             "Content-Type: application/octet-stream\r\n" +
176             "\r\n" +
177             "some random whatever\r\n" +
178             "--foo\r\n" +
179             "Content-Type: text/plain; charset=US-ASCII\r\n" +
180             "\r\n" +
181             "some random whatever\r\n" +
182             "--foo\r\n" +
183             "Content-Type: application/octet-stream\r\n" +
184             "\r\n" +
185             "some random whatever\r\n" +
186             "--foo--\r\n";
187         final String s = out.toString("US-ASCII");
188         Assertions.assertEquals(expected, s);
189         Assertions.assertEquals(-1, multipart.getTotalLength());
190     }
191 
192     @Test
193     public void testMultipartPartRFC6532() throws Exception {
194         tmpfile = File.createTempFile("tmp", ".bin");
195         try (Writer writer = new FileWriter(tmpfile)) {
196             writer.append("some random whatever");
197         }
198 
199         final MultipartPart p1 = MultipartPartBuilder.create(
200                 new FileBody(tmpfile)).build();
201         final MultipartPart p2 = MultipartPartBuilder.create(
202                 new FileBody(tmpfile, ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
203         @SuppressWarnings("resource")
204         final MultipartPart p3 = MultipartPartBuilder.create(
205                 new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
206         final HttpRFC6532Multipart multipart = new HttpRFC6532Multipart(null, "foo",
207                 Arrays.asList(p1, p2, p3));
208 
209         final ByteArrayOutputStream out = new ByteArrayOutputStream();
210         multipart.writeTo(out);
211         out.close();
212 
213         final String expected =
214             "--foo\r\n" +
215             "Content-Type: application/octet-stream\r\n" +
216             "\r\n" +
217             "some random whatever\r\n" +
218             "--foo\r\n" +
219             "Content-Type: text/plain; charset=US-ASCII\r\n" +
220             "\r\n" +
221             "some random whatever\r\n" +
222             "--foo\r\n" +
223             "Content-Type: application/octet-stream\r\n" +
224             "\r\n" +
225             "some random whatever\r\n" +
226             "--foo--\r\n";
227         final String s = out.toString("UTF-8");
228         Assertions.assertEquals(expected, s);
229         Assertions.assertEquals(-1, multipart.getTotalLength());
230     }
231 
232     private static final int SWISS_GERMAN_HELLO [] = {
233         0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
234     };
235 
236     private static final int RUSSIAN_HELLO [] = {
237         0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
238         0x432, 0x435, 0x442
239     };
240 
241     private static String constructString(final int [] unicodeChars) {
242         final StringBuilder buffer = new StringBuilder();
243         if (unicodeChars != null) {
244             for (final int unicodeChar : unicodeChars) {
245                 buffer.append((char)unicodeChar);
246             }
247         }
248         return buffer.toString();
249     }
250 
251     @Test
252     public void testMultipartPartBrowserCompatibleNonASCIIHeaders() throws Exception {
253         final String s1 = constructString(SWISS_GERMAN_HELLO);
254         final String s2 = constructString(RUSSIAN_HELLO);
255 
256         tmpfile = File.createTempFile("tmp", ".bin");
257         try (Writer writer = new FileWriter(tmpfile)) {
258             writer.append("some random whatever");
259         }
260 
261         @SuppressWarnings("resource")
262         final MultipartPart p1 = MultipartPartBuilder.create(
263                 new InputStreamBody(new FileInputStream(tmpfile), s1 + ".tmp")).build();
264         @SuppressWarnings("resource")
265         final MultipartPart p2 = MultipartPartBuilder.create(
266                 new InputStreamBody(new FileInputStream(tmpfile), s2 + ".tmp")).build();
267         final LegacyMultipart multipart = new LegacyMultipart(
268                 StandardCharsets.UTF_8, "foo",
269                 Arrays.asList(p1, p2));
270 
271         final ByteArrayOutputStream out = new ByteArrayOutputStream();
272         multipart.writeTo(out);
273         out.close();
274 
275         final String expected =
276             "--foo\r\n" +
277             "Content-Type: application/octet-stream\r\n" +
278             "\r\n" +
279             "some random whatever\r\n" +
280             "--foo\r\n" +
281             "Content-Type: application/octet-stream\r\n" +
282             "\r\n" +
283             "some random whatever\r\n" +
284             "--foo--\r\n";
285         final String s = out.toString("UTF-8");
286         Assertions.assertEquals(expected, s);
287         Assertions.assertEquals(-1, multipart.getTotalLength());
288     }
289 
290     @Test
291     public void testMultipartPartStringPartsMultiCharsets() throws Exception {
292         final String s1 = constructString(SWISS_GERMAN_HELLO);
293         final String s2 = constructString(RUSSIAN_HELLO);
294 
295         final MultipartPart p1 = MultipartPartBuilder.create(
296                 new StringBody(s1, ContentType.create("text/plain", StandardCharsets.ISO_8859_1))).build();
297         final MultipartPart p2 = MultipartPartBuilder.create(
298                 new StringBody(s2, ContentType.create("text/plain", Charset.forName("KOI8-R")))).build();
299         final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
300                 Arrays.asList(p1, p2));
301 
302         final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
303         multipart.writeTo(out1);
304         out1.close();
305 
306         final ByteArrayOutputStream out2 = new ByteArrayOutputStream();
307 
308         out2.write((
309             "--foo\r\n" +
310             "Content-Type: text/plain; charset=ISO-8859-1\r\n" +
311             "\r\n").getBytes(StandardCharsets.US_ASCII));
312         out2.write(s1.getBytes(StandardCharsets.ISO_8859_1));
313         out2.write(("\r\n" +
314             "--foo\r\n" +
315             "Content-Type: text/plain; charset=KOI8-R\r\n" +
316             "\r\n").getBytes(StandardCharsets.US_ASCII));
317         out2.write(s2.getBytes(Charset.forName("KOI8-R")));
318         out2.write(("\r\n" +
319             "--foo--\r\n").getBytes(StandardCharsets.US_ASCII));
320         out2.close();
321 
322         final byte[] actual = out1.toByteArray();
323         final byte[] expected = out2.toByteArray();
324 
325         Assertions.assertEquals(expected.length, actual.length);
326         for (int i = 0; i < actual.length; i++) {
327             Assertions.assertEquals(expected[i], actual[i]);
328         }
329         Assertions.assertEquals(expected.length, multipart.getTotalLength());
330     }
331 
332 }