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 }