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.assertTrue;
21  import static org.junit.Assert.fail;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.FilterInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStreamWriter;
31  import java.util.Iterator;
32  import java.util.List;
33  
34  import javax.servlet.http.HttpServletRequest;
35  
36  import org.apache.commons.fileupload.FileUploadBase.IOFileUploadException;
37  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
38  import org.apache.commons.fileupload.servlet.ServletFileUpload;
39  import org.apache.commons.fileupload.servlet.ServletRequestContext;
40  import org.junit.Test;
41  
42  /**
43   * Unit test for items with varying sizes.
44   */
45  public class StreamingTest {
46  
47      private String getFooter() {
48          return "-----1234--\r\n";
49      }
50  
51      private String getHeader(final String fieldName) {
52          return "-----1234\r\n"
53              + "Content-Disposition: form-data; name=\"" + fieldName + "\"\r\n"
54              + "\r\n";
55  
56      }
57  
58      private InputStream newInputStream(final ByteArrayInputStream bais) {
59          return new InputStream() {
60  
61              @Override
62              public int read() throws IOException {
63                  return bais.read();
64              }
65  
66              @Override
67              public int read(final byte[] b, final int off, final int len) throws IOException {
68                  return bais.read(b, off, Math.min(len, 3));
69              }
70  
71          };
72      }
73  
74      private byte[] newRequest() throws IOException {
75          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
76          try (OutputStreamWriter osw = new OutputStreamWriter(baos, "US-ASCII")) {
77              int add = 16;
78              int num = 0;
79              for (int i = 0; i < 16384; i += add) {
80                  if (++add == 32) {
81                      add = 16;
82                  }
83                  osw.write(getHeader("field" + num++));
84                  osw.flush();
85                  for (int j = 0; j < i; j++) {
86                      baos.write((byte) j);
87                  }
88                  osw.write("\r\n");
89              }
90              osw.write(getFooter());
91          }
92          return baos.toByteArray();
93      }
94  
95      private byte[] newShortRequest() throws IOException {
96          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
97          try (OutputStreamWriter osw = new OutputStreamWriter(baos, "US-ASCII")) {
98              osw.write(getHeader("field"));
99              osw.write("123");
100             osw.write("\r\n");
101             osw.write(getFooter());
102         }
103         return baos.toByteArray();
104     }
105 
106     private List<FileItem> parseUpload(final byte[] bytes) throws FileUploadException {
107         return parseUpload(new ByteArrayInputStream(bytes), bytes.length);
108     }
109 
110     private List<FileItem> parseUpload(final InputStream in, final int length) throws FileUploadException {
111         final String contentType = "multipart/form-data; boundary=---1234";
112         final FileUploadBase upload = new ServletFileUpload();
113         upload.setFileItemFactory(new DiskFileItemFactory());
114         final HttpServletRequest request = new MockHttpServletRequest(in, length, contentType);
115         return upload.parseRequest(new ServletRequestContext(request));
116     }
117 
118     private FileItemIterator parseUpload(final int length, final InputStream in) throws FileUploadException, IOException {
119         final String contentType = "multipart/form-data; boundary=---1234";
120         final FileUploadBase upload = new ServletFileUpload();
121         upload.setFileItemFactory(new DiskFileItemFactory());
122         final HttpServletRequest request = new MockHttpServletRequest(in, length, contentType);
123         return upload.getItemIterator(new ServletRequestContext(request));
124     }
125 
126     /**
127      * Tests a file upload with varying file sizes.
128      */
129     @Test
130     public void testFileUpload() throws IOException, FileUploadException {
131         final byte[] request = newRequest();
132         final List<FileItem> fileItems = parseUpload(request);
133         final Iterator<FileItem> fileIter = fileItems.iterator();
134         int add = 16;
135         int num = 0;
136         for (int i = 0; i < 16384; i += add) {
137             if (++add == 32) {
138                 add = 16;
139             }
140             final FileItem item = fileIter.next();
141             assertEquals("field" + num++, item.getFieldName());
142             final byte[] bytes = item.get();
143             assertEquals(i, bytes.length);
144             for (int j = 0; j < i; j++) {
145                 assertEquals((byte) j, bytes[j]);
146             }
147         }
148         assertTrue(!fileIter.hasNext());
149     }
150 
151     /**
152      * Test for FILEUPLOAD-135.
153      */
154     @Test
155     public void testFILEUPLOAD135() throws IOException, FileUploadException {
156         final byte[] request = newShortRequest();
157         final ByteArrayInputStream bais = new ByteArrayInputStream(request);
158         try (InputStream inputStream = newInputStream(bais)) {
159             final List<FileItem> fileItems = parseUpload(inputStream, request.length);
160             final Iterator<FileItem> fileIter = fileItems.iterator();
161             assertTrue(fileIter.hasNext());
162             final FileItem item = fileIter.next();
163             assertEquals("field", item.getFieldName());
164             final byte[] bytes = item.get();
165             assertEquals(3, bytes.length);
166             assertEquals((byte) '1', bytes[0]);
167             assertEquals((byte) '2', bytes[1]);
168             assertEquals((byte) '3', bytes[2]);
169             assertTrue(!fileIter.hasNext());
170         }
171     }
172 
173     /**
174      * Tests whether an invalid request throws a proper exception.
175      */
176     @Test
177     public void testFileUploadException() throws IOException, FileUploadException {
178         final byte[] request = newRequest();
179         final byte[] invalidRequest = new byte[request.length - 11];
180         System.arraycopy(request, 0, invalidRequest, 0, request.length - 11);
181         assertInstanceOf(MultipartStream.MalformedStreamException.class,
182                 assertThrows(IOFileUploadException.class, () -> parseUpload(invalidRequest)).getCause());
183     }
184 
185     /**
186      *  whether an {@link InvalidFileNameException} is thrown.
187      */
188     @Test
189     public void testInvalidFileNameException() throws Exception {
190         final String fileName = "foo.exe\u0000.png";
191         final String request =
192             "-----1234\r\n" +
193             "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" +
194             "Content-Type: text/whatever\r\n" +
195             "\r\n" +
196             "This is the content of the file\n" +
197             "\r\n" +
198             "-----1234\r\n" +
199             "Content-Disposition: form-data; name=\"field\"\r\n" +
200             "\r\n" +
201             "fieldValue\r\n" +
202             "-----1234\r\n" +
203             "Content-Disposition: form-data; name=\"multi\"\r\n" +
204             "\r\n" +
205             "value1\r\n" +
206             "-----1234\r\n" +
207             "Content-Disposition: form-data; name=\"multi\"\r\n" +
208             "\r\n" +
209             "value2\r\n" +
210             "-----1234--\r\n";
211         final byte[] reqBytes = request.getBytes("US-ASCII");
212 
213         final FileItemIterator fileItemIter = parseUpload(reqBytes.length, new ByteArrayInputStream(reqBytes));
214         final FileItemStream fileItemStream = fileItemIter.next();
215         InvalidFileNameException e = assertThrows(InvalidFileNameException.class, fileItemStream::getName);
216         assertEquals(fileName, e.getName());
217         assertTrue(e.getMessage().indexOf(fileName) == -1);
218         assertTrue(e.getMessage().indexOf("foo.exe\\0.png") != -1);
219 
220         final List<FileItem> fileItems = parseUpload(reqBytes);
221         final FileItem fileItem = fileItems.get(0);
222         e = assertThrows(InvalidFileNameException.class, fileItem::getName);
223         assertEquals(fileName, e.getName());
224         assertTrue(e.getMessage().indexOf(fileName) == -1);
225         assertTrue(e.getMessage().indexOf("foo.exe\\0.png") != -1);
226     }
227 
228     /**
229      * Tests, whether an IOException is properly delegated.
230      */
231     @Test
232     public void testIOException() throws IOException {
233         final byte[] request = newRequest();
234         final InputStream stream = new FilterInputStream(new ByteArrayInputStream(request)) {
235 
236             private int num;
237 
238             @Override
239             public int read() throws IOException {
240                 if (++num > 123) {
241                     throw new IOException("123");
242                 }
243                 return super.read();
244             }
245 
246             @Override
247             public int read(final byte[] buffer, final int offset, final int length) throws IOException {
248                 for (int i = 0; i < length; i++) {
249                     final int res = read();
250                     if (res == -1) {
251                         return i == 0 ? -1 : i;
252                     }
253                     buffer[offset + i] = (byte) res;
254                 }
255                 return length;
256             }
257         };
258         final FileUploadException e = assertThrows(FileUploadException.class, () -> parseUpload(stream, request.length));
259         assertTrue(e.getCause() instanceof IOException);
260         assertEquals("123", e.getCause().getMessage());
261     }
262 }