View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.fileupload;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertFalse;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.UnsupportedEncodingException;
29  import java.nio.charset.StandardCharsets;
30  import java.util.List;
31  
32  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
33  import org.apache.commons.fileupload.portlet.PortletFileUploadTest;
34  import org.apache.commons.fileupload.servlet.ServletFileUpload;
35  import org.apache.commons.fileupload.servlet.ServletFileUploadTest;
36  import org.apache.commons.fileupload.util.Streams;
37  import org.junit.Test;
38  import org.junit.runner.RunWith;
39  import org.junit.runners.Parameterized;
40  import org.junit.runners.Parameterized.Parameter;
41  import org.junit.runners.Parameterized.Parameters;
42  
43  /**
44   * Common tests for implementations of {@link FileUpload}. This is a parameterized test.
45   * Tests must be valid and common to all implementations of FileUpload added as parameter
46   * in this class.
47   *
48   * @see ServletFileUploadTest
49   * @see PortletFileUploadTest
50   * @since 1.4
51   */
52  @RunWith(Parameterized.class)
53  public class FileUploadTest {
54  
55      /**
56       * @return {@link FileUpload} classes under test.
57       */
58      @Parameters(name = "{0}")
59      public static Iterable<? extends Object> data() {
60          return Util.fileUploadImplementations();
61      }
62  
63      /**
64       * Current parameterized FileUpload.
65       */
66      @Parameter
67      public FileUpload upload;
68  
69      private void assertHeaders(final String[] headerNames, final String[] headerValues, final FileItem item, final int index) {
70          for (int i = 0; i < headerNames.length; i++) {
71              final String value = item.getHeaders().getHeader(headerNames[i]);
72              if (i == index) {
73                  assertEquals(headerValues[i], value);
74              } else {
75                  assertNull(value);
76              }
77          }
78      }
79  
80      /**
81       * Test for <a href="http://issues.apache.org/jira/browse/FILEUPLOAD-239">FILEUPLOAD-239</a>.
82       */
83      @Test
84      public void testContentTypeAttachment()
85              throws IOException, FileUploadException {
86          final List<FileItem> fileItems = Util.parseUpload(upload,
87                  "-----1234\r\n" +
88                  "content-disposition: form-data; name=\"field1\"\r\n" +
89                  "\r\n" +
90                  "Joe Blow\r\n" +
91                  "-----1234\r\n" +
92                  "content-disposition: form-data; name=\"pics\"\r\n" +
93                  "Content-type: multipart/mixed, boundary=---9876\r\n" +
94                  "\r\n" +
95                  "-----9876\r\n" +
96                  "Content-disposition: attachment; filename=\"file1.txt\"\r\n" +
97                  "Content-Type: text/plain\r\n" +
98                  "\r\n" +
99                  "... contents of file1.txt ...\r\n" +
100                 "-----9876--\r\n" +
101                 "-----1234--\r\n");
102         assertEquals(2, fileItems.size());
103 
104         final FileItem field = fileItems.get(0);
105         assertEquals("field1", field.getFieldName());
106         assertTrue(field.isFormField());
107         assertEquals("Joe Blow", field.getString());
108 
109         final FileItem file = fileItems.get(1);
110         assertEquals("pics", file.getFieldName());
111         assertFalse(file.isFormField());
112         assertEquals("... contents of file1.txt ...", file.getString());
113         assertEquals("text/plain", file.getContentType());
114         assertEquals("file1.txt", file.getName());
115     }
116 
117     /**
118      * This is what the browser does if you submit the form without choosing a file.
119      */
120     @Test
121     public void testEmptyFile()
122             throws UnsupportedEncodingException, FileUploadException {
123         final List<FileItem> fileItems = Util.parseUpload (upload,
124                                                 "-----1234\r\n" +
125                                                 "Content-Disposition: form-data; name=\"file\"; filename=\"\"\r\n" +
126                                                 "\r\n" +
127                                                 "\r\n" +
128                                                 "-----1234--\r\n");
129         assertEquals(1, fileItems.size());
130 
131         final FileItem file = fileItems.get(0);
132         assertFalse(file.isFormField());
133         assertEquals("", file.getString());
134         assertEquals("", file.getName());
135     }
136 
137     @Test
138     public void testFilenameCaseSensitivity()
139             throws IOException, FileUploadException {
140         final List<FileItem> fileItems = Util.parseUpload(upload,
141                                                "-----1234\r\n" +
142                                                "Content-Disposition: form-data; name=\"FiLe\"; filename=\"FOO.tab\"\r\n" +
143                                                "Content-Type: text/whatever\r\n" +
144                                                "\r\n" +
145                                                "This is the content of the file\n" +
146                                                "\r\n" +
147                                                "-----1234--\r\n");
148         assertEquals(1, fileItems.size());
149 
150         final FileItem file = fileItems.get(0);
151         assertEquals("FiLe", file.getFieldName());
152         assertEquals("FOO.tab", file.getName());
153     }
154 
155     @Test
156     public void testFileUpload()
157             throws IOException, FileUploadException {
158         final List<FileItem> fileItems = Util.parseUpload(upload,
159                                                "-----1234\r\n" +
160                                                "Content-Disposition: form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
161                                                "Content-Type: text/whatever\r\n" +
162                                                "\r\n" +
163                                                "This is the content of the file\n" +
164                                                "\r\n" +
165                                                "-----1234\r\n" +
166                                                "Content-Disposition: form-data; name=\"field\"\r\n" +
167                                                "\r\n" +
168                                                "fieldValue\r\n" +
169                                                "-----1234\r\n" +
170                                                "Content-Disposition: form-data; name=\"multi\"\r\n" +
171                                                "\r\n" +
172                                                "value1\r\n" +
173                                                "-----1234\r\n" +
174                                                "Content-Disposition: form-data; name=\"multi\"\r\n" +
175                                                "\r\n" +
176                                                "value2\r\n" +
177                                                "-----1234--\r\n");
178         assertEquals(4, fileItems.size());
179 
180         final FileItem file = fileItems.get(0);
181         assertEquals("file", file.getFieldName());
182         assertFalse(file.isFormField());
183         assertEquals("This is the content of the file\n", file.getString());
184         assertEquals("text/whatever", file.getContentType());
185         assertEquals("foo.tab", file.getName());
186 
187         final FileItem field = fileItems.get(1);
188         assertEquals("field", field.getFieldName());
189         assertTrue(field.isFormField());
190         assertEquals("fieldValue", field.getString());
191 
192         final FileItem multi0 = fileItems.get(2);
193         assertEquals("multi", multi0.getFieldName());
194         assertTrue(multi0.isFormField());
195         assertEquals("value1", multi0.getString());
196 
197         final FileItem multi1 = fileItems.get(3);
198         assertEquals("multi", multi1.getFieldName());
199         assertTrue(multi1.isFormField());
200         assertEquals("value2", multi1.getString());
201     }
202 
203     /**
204      * Tests <a href="http://issues.apache.org/jira/browse/FILEUPLOAD-130">FILEUPLOAD-130</a>.
205      */
206     @Test
207     public void testFileUpload130()
208             throws Exception {
209         final String[] headerNames = {
210             "SomeHeader", "OtherHeader", "YetAnotherHeader", "WhatAHeader"
211         };
212         final String[] headerValues = {
213             "present", "Is there", "Here", "Is That"
214         };
215         final List<FileItem> fileItems = Util.parseUpload(upload,
216                                                "-----1234\r\n" +
217                                                "Content-Disposition: form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
218                                                "Content-Type: text/whatever\r\n" +
219                                                headerNames[0] + ": " + headerValues[0] + "\r\n" +
220                                                "\r\n" +
221                                                "This is the content of the file\n" +
222                                                "\r\n" +
223                                                "-----1234\r\n" +
224                                                "Content-Disposition: form-data; \r\n" +
225                                                "\tname=\"field\"\r\n" +
226                                                headerNames[1] + ": " + headerValues[1] + "\r\n" +
227                                                "\r\n" +
228                                                "fieldValue\r\n" +
229                                                "-----1234\r\n" +
230                                                "Content-Disposition: form-data;\r\n" +
231                                                "     name=\"multi\"\r\n" +
232                                                headerNames[2] + ": " + headerValues[2] + "\r\n" +
233                                                "\r\n" +
234                                                "value1\r\n" +
235                                                "-----1234\r\n" +
236                                                "Content-Disposition: form-data; name=\"multi\"\r\n" +
237                                                headerNames[3] + ": " + headerValues[3] + "\r\n" +
238                                                "\r\n" +
239                                                "value2\r\n" +
240                                                "-----1234--\r\n");
241         assertEquals(4, fileItems.size());
242 
243         final FileItem file = fileItems.get(0);
244         assertHeaders(headerNames, headerValues, file, 0);
245 
246         final FileItem field = fileItems.get(1);
247         assertHeaders(headerNames, headerValues, field, 1);
248 
249         final FileItem multi0 = fileItems.get(2);
250         assertHeaders(headerNames, headerValues, multi0, 2);
251 
252         final FileItem multi1 = fileItems.get(3);
253         assertHeaders(headerNames, headerValues, multi1, 3);
254     }
255 
256     /**
257      * Tests for <a href="http://issues.apache.org/jira/browse/FILEUPLOAD-62">FILEUPLOAD-62</a>.
258      */
259     @Test
260     public void testFILEUPLOAD62() throws Exception {
261         final String contentType = "multipart/form-data; boundary=AaB03x";
262         final String request =
263             "--AaB03x\r\n" +
264             "content-disposition: form-data; name=\"field1\"\r\n" +
265             "\r\n" +
266             "Joe Blow\r\n" +
267             "--AaB03x\r\n" +
268             "content-disposition: form-data; name=\"pics\"\r\n" +
269             "Content-type: multipart/mixed; boundary=BbC04y\r\n" +
270             "\r\n" +
271             "--BbC04y\r\n" +
272             "Content-disposition: attachment; filename=\"file1.txt\"\r\n" +
273             "Content-Type: text/plain\r\n" +
274             "\r\n" +
275             "... contents of file1.txt ...\r\n" +
276             "--BbC04y\r\n" +
277             "Content-disposition: attachment; filename=\"file2.gif\"\r\n" +
278             "Content-type: image/gif\r\n" +
279             "Content-Transfer-Encoding: binary\r\n" +
280             "\r\n" +
281             "...contents of file2.gif...\r\n" +
282             "--BbC04y--\r\n" +
283             "--AaB03x--";
284         final List<FileItem> fileItems = Util.parseUpload(upload, request.getBytes("US-ASCII"), contentType);
285         assertEquals(3, fileItems.size());
286         final FileItem item0 = fileItems.get(0);
287         assertEquals("field1", item0.getFieldName());
288         assertNull(item0.getName());
289         assertEquals("Joe Blow", new String(item0.get()));
290         final FileItem item1 = fileItems.get(1);
291         assertEquals("pics", item1.getFieldName());
292         assertEquals("file1.txt", item1.getName());
293         assertEquals("... contents of file1.txt ...", new String(item1.get()));
294         final FileItem item2 = fileItems.get(2);
295         assertEquals("pics", item2.getFieldName());
296         assertEquals("file2.gif", item2.getName());
297         assertEquals("...contents of file2.gif...", new String(item2.get()));
298     }
299 
300     /**
301      * Test for <a href="http://issues.apache.org/jira/browse/FILEUPLOAD-111">FILEUPLOAD-111</a>.
302      */
303     @Test
304     public void testFoldedHeaders()
305             throws IOException, FileUploadException {
306         final List<FileItem> fileItems = Util.parseUpload(upload, "-----1234\r\n" +
307                 "Content-Disposition: form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
308                 "Content-Type: text/whatever\r\n" +
309                 "\r\n" +
310                 "This is the content of the file\n" +
311                 "\r\n" +
312                 "-----1234\r\n" +
313                 "Content-Disposition: form-data; \r\n" +
314                 "\tname=\"field\"\r\n" +
315                 "\r\n" +
316                 "fieldValue\r\n" +
317                 "-----1234\r\n" +
318                 "Content-Disposition: form-data;\r\n" +
319                 "     name=\"multi\"\r\n" +
320                 "\r\n" +
321                 "value1\r\n" +
322                 "-----1234\r\n" +
323                 "Content-Disposition: form-data; name=\"multi\"\r\n" +
324                 "\r\n" +
325                 "value2\r\n" +
326                 "-----1234--\r\n");
327         assertEquals(4, fileItems.size());
328 
329         final FileItem file = fileItems.get(0);
330         assertEquals("file", file.getFieldName());
331         assertFalse(file.isFormField());
332         assertEquals("This is the content of the file\n", file.getString());
333         assertEquals("text/whatever", file.getContentType());
334         assertEquals("foo.tab", file.getName());
335 
336         final FileItem field = fileItems.get(1);
337         assertEquals("field", field.getFieldName());
338         assertTrue(field.isFormField());
339         assertEquals("fieldValue", field.getString());
340 
341         final FileItem multi0 = fileItems.get(2);
342         assertEquals("multi", multi0.getFieldName());
343         assertTrue(multi0.isFormField());
344         assertEquals("value1", multi0.getString());
345 
346         final FileItem multi1 = fileItems.get(3);
347         assertEquals("multi", multi1.getFieldName());
348         assertTrue(multi1.isFormField());
349         assertEquals("value2", multi1.getString());
350     }
351 
352     /**
353      * Internet Explorer 5 for the Mac has a bug where the carriage
354      * return is missing on any boundary line immediately preceding
355      * an input with type=image. (type=submit does not have the bug.)
356      */
357     @Test
358     public void testIE5MacBug()
359             throws UnsupportedEncodingException, FileUploadException {
360         final List<FileItem> fileItems = Util.parseUpload(upload,
361                                                "-----1234\r\n" +
362                                                "Content-Disposition: form-data; name=\"field1\"\r\n" +
363                                                "\r\n" +
364                                                "fieldValue\r\n" +
365                                                "-----1234\n" + // NOTE \r missing
366                                                "Content-Disposition: form-data; name=\"submitName.x\"\r\n" +
367                                                "\r\n" +
368                                                "42\r\n" +
369                                                "-----1234\n" + // NOTE \r missing
370                                                "Content-Disposition: form-data; name=\"submitName.y\"\r\n" +
371                                                "\r\n" +
372                                                "21\r\n" +
373                                                "-----1234\r\n" +
374                                                "Content-Disposition: form-data; name=\"field2\"\r\n" +
375                                                "\r\n" +
376                                                "fieldValue2\r\n" +
377                                                "-----1234--\r\n");
378 
379         assertEquals(4, fileItems.size());
380 
381         final FileItem field1 = fileItems.get(0);
382         assertEquals("field1", field1.getFieldName());
383         assertTrue(field1.isFormField());
384         assertEquals("fieldValue", field1.getString());
385 
386         final FileItem submitX = fileItems.get(1);
387         assertEquals("submitName.x", submitX.getFieldName());
388         assertTrue(submitX.isFormField());
389         assertEquals("42", submitX.getString());
390 
391         final FileItem submitY = fileItems.get(2);
392         assertEquals("submitName.y", submitY.getFieldName());
393         assertTrue(submitY.isFormField());
394         assertEquals("21", submitY.getString());
395 
396         final FileItem field2 = fileItems.get(3);
397         assertEquals("field2", field2.getFieldName());
398         assertTrue(field2.isFormField());
399         assertEquals("fieldValue2", field2.getString());
400     }
401 
402     /**
403      * Test for multipart/related without any content-disposition Header.
404      * This kind of Content-Type is commonly used by SOAP-Requests with Attachments (MTOM)
405      */
406     @Test
407     public void testMultipartRelated() throws FileUploadException {
408         final String soapEnvelope =
409                 "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">\r\n" +
410                 "  <soap:Header></soap:Header>\r\n" +
411                 "  <soap:Body>\r\n" +
412                 "    <ns1:Test xmlns:ns1=\"http://www.test.org/some-test-namespace\">\r\n" +
413                 "      <ns1:Attachment>\r\n" +
414                 "        <xop:Include xmlns:xop=\"http://www.w3.org/2004/08/xop/include\"" +
415                 " href=\"ref-to-attachment%40some.domain.org\"/>\r\n" +
416                 "      </ns1:Attachment>\r\n" +
417                 "    </ns1:Test>\r\n" +
418                 "  </soap:Body>\r\n" +
419                 "</soap:Envelope>";
420 
421         final String content = "-----1234\r\n" +
422                 "content-type: application/xop+xml; type=\"application/soap+xml\"\r\n" +
423                 "\r\n" +
424                 soapEnvelope + "\r\n" +
425                 "-----1234\r\n" +
426                 "Content-type: text/plain\r\n" +
427                 "content-id: <ref-to-attachment@some.domain.org>\r\n" +
428                 "\r\n" +
429                 "some text/plain content\r\n" +
430                 "-----1234--\r\n";
431 
432         final List<FileItem> fileItems = Util.parseUpload(upload, content.getBytes(StandardCharsets.US_ASCII),
433                 "multipart/related; boundary=---1234;" +
434                     " type=\"application/xop+xml\"; start-info=\"application/soap+xml\"");
435         assertEquals(2, fileItems.size());
436 
437         final FileItem part1 = fileItems.get(0);
438         assertNull(part1.getFieldName());
439         assertFalse(part1.isFormField());
440         assertEquals(soapEnvelope, part1.getString());
441 
442         final FileItem part2 = fileItems.get(1);
443         assertNull(part2.getFieldName());
444         assertFalse(part2.isFormField());
445         assertEquals("some text/plain content", part2.getString());
446         assertEquals("text/plain", part2.getContentType());
447         assertNull(part2.getName());
448     }
449 
450 
451     @Test
452     public void testOpenStreamSecondCall() throws IOException, FileUploadException {
453         final String request =
454             "-----1234\r\n" +
455             "Content-Disposition: form-data; name=\"file1\"; filename=\"foo1.tab\"\r\n" +
456             "Content-Type: text/whatever\r\n" +
457             "Content-Length: 10\r\n" +
458             "\r\n" +
459             "This is the content of the file\n" +
460             "\r\n" +
461             "-----1234--\r\n";
462 
463         final ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
464         upload.setFileSizeMax(-1);
465         upload.setSizeMax(300);
466 
467         // the first item should be within the max size limit
468         // set the read limit to 10 to simulate a "real" stream
469         // otherwise the buffer would be immediately filled
470 
471         final MockHttpServletRequest req = new MockHttpServletRequest(
472                 request.getBytes("US-ASCII"), Constants.CONTENT_TYPE);
473         req.setContentLength(-1);
474         req.setReadLimit(10);
475 
476         final FileItemIterator it = upload.getItemIterator(req);
477         assertTrue(it.hasNext());
478 
479         final FileItemStream item = it.next();
480         assertFalse(item.isFormField());
481         assertEquals("file1", item.getFieldName());
482         assertEquals("foo1.tab", item.getName());
483 
484         try (InputStream stream = item.openStream()) {
485             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
486             Streams.copy(stream, baos, true);
487         }
488 
489         assertThrows(IOException.class, item::openStream, "Attempt to open a closed stream did not throw an exception");
490         // Should only be one item
491         assertFalse(it.hasNext());
492     }
493 }