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 }