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.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  public class TestMultipartEntityBuilder {
44  
45      @Test
46      public void testBasics() throws Exception {
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      public void testMultipartOptions() throws Exception {
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      public void testAddBodyParts() throws Exception {
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      public void testMultipartCustomContentType() throws Exception {
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      public void testMultipartContentTypeParameter() throws Exception {
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     public void testMultipartDefaultContentTypeOmitsCharset() throws Exception {
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     public void testMultipartFormDataContentTypeOmitsCharset() throws Exception {
120         // Note: org.apache.hc.core5.http.ContentType.MULTIPART_FORM_DATA uses StandardCharsets.ISO_8859_1,
121         // so we create a custom ContentType here
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     public void testMultipartCustomContentTypeParameterOverrides() throws Exception {
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     public 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     public 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     public 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     public 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     public 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     public 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 }