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.fileupload2.core;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.FilterInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStreamWriter;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.InvalidPathException;
31  import java.util.List;
32  
33  import org.junit.jupiter.api.Test;
34  
35  /**
36   * Unit test for items with varying sizes.
37   *
38   * @param <AFU> The subclass of FileUpload.
39   * @param <R>   The type of FileUpload request.
40   * @param <C>   The request context type.
41   * @param <I>   The FileItem type.
42   * @param <F>   The FileItemFactory type.
43   */
44  public abstract class AbstractStreamingTest<AFU extends AbstractFileUpload<R, I, F>, R, C extends AbstractRequestContext<?>, I extends FileItem<I>, F extends FileItemFactory<I>>
45          extends AbstractTest<AFU, R, I, F> {
46  
47      protected String getFooter() {
48          return "-----1234--\r\n";
49      }
50  
51      protected String getHeader(final String value) {
52          // @formatter:off
53          return "-----1234\r\n"
54              + "Content-Disposition: form-data; name=\"" + value + "\"\r\n"
55              + "\r\n";
56          // @formatter:on
57      }
58  
59      protected abstract F newDiskFileItemFactory();
60  
61      protected byte[] newRequest() throws IOException {
62          final var baos = new ByteArrayOutputStream();
63          try (final var osw = new OutputStreamWriter(baos, StandardCharsets.US_ASCII)) {
64              var add = 16;
65              var num = 0;
66              for (var i = 0; i < 16384; i += add) {
67                  if (++add == 32) {
68                      add = 16;
69                  }
70                  osw.write(getHeader("field" + num++));
71                  osw.flush();
72                  for (var j = 0; j < i; j++) {
73                      baos.write((byte) j);
74                  }
75                  osw.write("\r\n");
76              }
77              osw.write(getFooter());
78          }
79          return baos.toByteArray();
80      }
81  
82      protected abstract C newServletRequestContext(final R request);
83  
84      protected byte[] newShortRequest() throws IOException {
85          final var baos = new ByteArrayOutputStream();
86          try (final var osw = new OutputStreamWriter(baos, StandardCharsets.US_ASCII)) {
87              osw.write(getHeader("field"));
88              osw.write("123");
89              osw.write("\r\n");
90              osw.write(getFooter());
91          }
92          return baos.toByteArray();
93      }
94  
95      protected List<I> parseUpload(final byte[] bytes) throws FileUploadException {
96          return parseUpload(new ByteArrayInputStream(bytes), bytes.length);
97      }
98  
99      protected List<I> parseUpload(final InputStream inputStream, final int length) throws FileUploadException {
100         final var contentType = "multipart/form-data; boundary=---1234";
101 
102         final var upload = newFileUpload();
103         upload.setFileItemFactory(newDiskFileItemFactory());
104         final var request = newMockHttpServletRequest(inputStream, length, contentType, -1);
105 
106         return upload.parseRequest(newServletRequestContext(request));
107     }
108 
109     protected FileItemInputIterator parseUpload(final int length, final InputStream inputStream) throws FileUploadException, IOException {
110         final var contentType = "multipart/form-data; boundary=---1234";
111 
112         final var upload = newFileUpload();
113         upload.setFileItemFactory(newDiskFileItemFactory());
114         final var request = newMockHttpServletRequest(inputStream, length, contentType, -1);
115 
116         return upload.getItemIterator(newServletRequestContext(request));
117     }
118 
119     /**
120      * Tests a file upload with varying file sizes.
121      *
122      * @throws IOException Test failure.
123      */
124     @Test
125     public void testFileUpload() throws IOException {
126         final var request = newRequest();
127         final var fileItems = parseUpload(request);
128         final var fileIter = fileItems.iterator();
129         var add = 16;
130         var num = 0;
131         for (var i = 0; i < 16384; i += add) {
132             if (++add == 32) {
133                 add = 16;
134             }
135             final var item = fileIter.next();
136             assertEquals("field" + num++, item.getFieldName());
137             final var bytes = item.get();
138             assertEquals(i, bytes.length);
139             for (var j = 0; j < i; j++) {
140                 assertEquals((byte) j, bytes[j]);
141             }
142         }
143         assertTrue(!fileIter.hasNext());
144     }
145 
146     /**
147      * Test for FILEUPLOAD-135
148      *
149      * @throws IOException Test failure.
150      */
151     @Test
152     public void testFILEUPLOAD135() throws IOException {
153         final var request = newShortRequest();
154         final var bais = new ByteArrayInputStream(request);
155         final var fileItems = parseUpload(new InputStream() {
156             @Override
157             public int read() throws IOException {
158                 return bais.read();
159             }
160 
161             @Override
162             public int read(final byte[] b, final int off, final int len) throws IOException {
163                 return bais.read(b, off, Math.min(len, 3));
164             }
165 
166         }, request.length);
167         final var fileIter = fileItems.iterator();
168         assertTrue(fileIter.hasNext());
169         final var item = fileIter.next();
170         assertEquals("field", item.getFieldName());
171         final var bytes = item.get();
172         assertEquals(3, bytes.length);
173         assertEquals((byte) '1', bytes[0]);
174         assertEquals((byte) '2', bytes[1]);
175         assertEquals((byte) '3', bytes[2]);
176         assertTrue(!fileIter.hasNext());
177     }
178 
179     /**
180      * Tests, whether an invalid request throws a proper exception.
181      *
182      * @throws IOException Test failure.
183      */
184     @Test
185     public void testFileUploadException() throws IOException {
186         final var request = newRequest();
187         final var invalidRequest = new byte[request.length - 11];
188         System.arraycopy(request, 0, invalidRequest, 0, request.length - 11);
189         try {
190             parseUpload(invalidRequest);
191             fail("Expected EndOfStreamException");
192         } catch (final FileUploadException e) {
193             assertTrue(e.getSuppressed()[0] instanceof MultipartInput.MalformedStreamException, e.toString());
194         }
195     }
196 
197     /**
198      * Tests, whether an {@link InvalidPathException} is thrown.
199      *
200      * @throws IOException Test failure.
201      */
202     @Test
203     public void testInvalidFileNameException() throws IOException {
204         final var fileName = "foo.exe\u0000.png";
205         // @formatter:off
206         final var request =
207             "-----1234\r\n" +
208             "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" +
209             "Content-Type: text/whatever\r\n" +
210             "\r\n" +
211             "This is the content of the file\n" +
212             "\r\n" +
213             "-----1234\r\n" +
214             "Content-Disposition: form-data; name=\"field\"\r\n" +
215             "\r\n" +
216             "fieldValue\r\n" +
217             "-----1234\r\n" +
218             "Content-Disposition: form-data; name=\"multi\"\r\n" +
219             "\r\n" +
220             "value1\r\n" +
221             "-----1234\r\n" +
222             "Content-Disposition: form-data; name=\"multi\"\r\n" +
223             "\r\n" +
224             "value2\r\n" +
225             "-----1234--\r\n";
226         // @formatter:on
227         final var reqBytes = request.getBytes(StandardCharsets.US_ASCII);
228 
229         final var fileItemIter = parseUpload(reqBytes.length, new ByteArrayInputStream(reqBytes));
230         final var fileItemInput = fileItemIter.next();
231         try {
232             fileItemInput.getName();
233             fail("Expected exception");
234         } catch (final InvalidPathException e) {
235             assertEquals(fileName, e.getInput());
236             assertEquals(26, e.getMessage().indexOf(fileName));
237             assertEquals(7, e.getIndex());
238             assertTrue(e.getMessage().contains("foo.exe\\0.png"));
239         }
240 
241         try {
242             parseUpload(reqBytes);
243             fail("Expected exception");
244         } catch (final InvalidPathException e) {
245             assertEquals(fileName, e.getInput());
246             assertEquals(26, e.getMessage().indexOf(fileName));
247             assertEquals(7, e.getIndex());
248             assertTrue(e.getMessage().contains("foo.exe\\0.png"));
249         }
250     }
251 
252     /**
253      * Tests, whether an IOException is properly delegated.
254      *
255      * @throws IOException Test failure.
256      */
257     @Test
258     public void testIOException() throws IOException {
259         final var request = newRequest();
260         final InputStream stream = new FilterInputStream(new ByteArrayInputStream(request)) {
261             private int num;
262 
263             @Override
264             public int read() throws IOException {
265                 if (++num > 123) {
266                     throw new IOException("123");
267                 }
268                 return super.read();
269             }
270 
271             @Override
272             public int read(final byte[] buffer, final int offset, final int length) throws IOException {
273                 for (var i = 0; i < length; i++) {
274                     final var res = read();
275                     if (res == -1) {
276                         return i == 0 ? -1 : i;
277                     }
278                     buffer[offset + i] = (byte) res;
279                 }
280                 return length;
281             }
282         };
283         try {
284             parseUpload(stream, request.length);
285             fail("Expected IOException");
286         } catch (final FileUploadException e) {
287             assertTrue(e.getCause() instanceof IOException);
288             assertEquals("123", e.getCause().getMessage());
289         }
290     }
291 
292 }