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.assertFalse;
    21  import static org.junit.jupiter.api.Assertions.assertNotNull;
    22  import static org.junit.jupiter.api.Assertions.assertThrows;
    23  import static org.junit.jupiter.api.Assertions.assertTrue;
    24
    25  import java.io.ByteArrayOutputStream;
    26  import java.io.IOException;
    27  import java.io.ObjectOutputStream;
    28  import java.nio.file.FileVisitResult;
    29  import java.nio.file.Files;
    30  import java.nio.file.InvalidPathException;
    31  import java.nio.file.Path;
    32  import java.nio.file.attribute.BasicFileAttributes;
    33
    34  import org.apache.commons.io.file.PathUtils;
    35  import org.apache.commons.io.file.SimplePathVisitor;
    36  import org.apache.commons.lang3.SerializationUtils;
    37  import org.junit.jupiter.api.AfterEach;
    38  import org.junit.jupiter.api.BeforeEach;
    39  import org.junit.jupiter.api.Test;
    40
    41  /**
    42   * Serialization Unit tests for {@link DiskFileItem}.
    43   */
    44  public class DiskFileItemSerializeTest {
    45
    46      /**
    47       * Use a private repository to catch any files left over by tests.
    48       */
    49      private static final Path REPOSITORY = PathUtils.getTempDirectory().resolve("DiskFileItemRepo");
    50
    51      /**
    52       * Content type for regular form items.
    53       */
    54      private static final String TEXT_CONTENT_TYPE = "text/plain";
    55
    56      /**
    57       * Very low threshold for testing memory versus disk options.
    58       */
    59      private static final int THRESHOLD = 16;
    60
    61      /**
    62       * Compare content bytes.
    63       */
    64      private void compareBytes(final String text, final byte[] origBytes, final byte[] newBytes) {
    65          assertNotNull(origBytes, "origBytes must not be null");
    66          assertNotNull(newBytes, "newBytes must not be null");
    67          assertEquals(origBytes.length, newBytes.length, text + " byte[] length");
    68          for (var i = 0; i < origBytes.length; i++) {
    69              assertEquals(origBytes[i], newBytes[i], text + " byte[" + i + "]");
    70          }
    71      }
    72
    73      /**
    74       * Create content bytes of a specified size.
    75       */
    76      private byte[] createContentBytes(final int size) {
    77          final var buffer = new StringBuilder(size);
    78          byte count = 0;
    79          for (var i = 0; i < size; i++) {
    80              buffer.append(count + "");
    81              count++;
    82              if (count > 9) {
    83                  count = 0;
    84              }
    85          }
    86          return buffer.toString().getBytes();
    87      }
    88
    89      /**
    90       * Create a FileItem with the specfied content bytes.
    91       */
    92      private DiskFileItem createFileItem(final byte[] contentBytes) throws IOException {
    93          return createFileItem(contentBytes, REPOSITORY);
    94      }
    95
    96      /**
    97       * Create a FileItem with the specfied content bytes and repository.
    98       */
    99      private DiskFileItem createFileItem(final byte[] contentBytes, final Path repository) throws IOException {
    100         // @formatter:off
    101         final FileItemFactory<DiskFileItem> factory = DiskFileItemFactory.builder()
    102                 .setBufferSize(THRESHOLD)
    103                 .setPath(repository)
    104                 .get();
    105         // @formatter:on
    106         // @formatter:off
    107         final var item = factory.fileItemBuilder()
    108                 .setFieldName("textField")
    109                 .setContentType(TEXT_CONTENT_TYPE)
    110                 .setFormField(true)
    111                 .setFileName("My File Name")
    112                 .get();
    113         // @formatter:on
    114
    115         try (var os = item.getOutputStream()) {
    116             os.write(contentBytes);
    117         }
    118         return item;
    119     }
    120
    121     /**
    122      * Deserializes.
    123      */
    124     private Object deserialize(final ByteArrayOutputStream baos) {
    125         return SerializationUtils.deserialize(baos.toByteArray());
    126     }
    127
    128     /**
    129      * Serializes.
    130      */
    131     private ByteArrayOutputStream serialize(final Object target) throws IOException {
    132         try (final var baos = new ByteArrayOutputStream();
    133                 final var oos = new ObjectOutputStream(baos)) {
    134             oos.writeObject(target);
    135             oos.flush();
    136             return baos;
    137         }
    138     }
    139
    140     @BeforeEach
    141     public void setUp() throws IOException {
    142         if (Files.exists(REPOSITORY)) {
    143             PathUtils.deleteDirectory(REPOSITORY);
    144         } else {
    145             Files.createDirectories(REPOSITORY);
    146         }
    147     }
    148
    149     @AfterEach
    150     public void tearDown() throws IOException {
    151         if (Files.exists(REPOSITORY)) {
    152             PathUtils.visitFileTree(new SimplePathVisitor() {
    153                 @Override
    154                 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
    155                     System.out.println("Found leftover file " + file);
    156                     return FileVisitResult.CONTINUE;
    157                 }
    158
    159             }, REPOSITORY);
    160             PathUtils.deleteDirectory(REPOSITORY);
    161         }
    162     }
    163
    164     /**
    165      * Test creation of a field for which the amount of data falls above the configured threshold.
    166      *
    167      * @throws IOException Test failure.
    168      */
    169     @Test
    170     public void testAboveThreshold() throws IOException {
    171         // Create the FileItem
    172         final var testFieldValueBytes = createContentBytes(THRESHOLD + 1);
    173         final var item = createFileItem(testFieldValueBytes);
    174
    175         // Check state is as expected
    176         assertFalse(item.isInMemory(), "Initial: in memory");
    177         assertEquals(item.getSize(), testFieldValueBytes.length, "Initial: size");
    178         compareBytes("Initial", item.get(), testFieldValueBytes);
    179
    180         testWritingToFile(item, testFieldValueBytes);
    181         item.delete();
    182     }
    183
    184     /**
    185      * Test creation of a field for which the amount of data falls below the configured threshold.
    186      *
    187      * @throws IOException Test failure.
    188      */
    189     @Test
    190     public void testBelowThreshold() throws IOException {
    191         // Create the FileItem
    192         final var testFieldValueBytes = createContentBytes(THRESHOLD - 1);
    193         testInMemoryObject(testFieldValueBytes);
    194     }
    195
    196     @Test
    197     public void testCheckFileName() {
    198         assertThrows(InvalidPathException.class, () -> DiskFileItem.checkFileName("\0"));
    199     }
    200
    201     /**
    202      * Helper method to test creation of a field.
    203      */
    204     private void testInMemoryObject(final byte[] testFieldValueBytes) throws IOException {
    205         testInMemoryObject(testFieldValueBytes, REPOSITORY);
    206     }
    207
    208     /**
    209      * Helper method to test creation of a field when a repository is used.
    210      */
    211     private void testInMemoryObject(final byte[] testFieldValueBytes, final Path repository) throws IOException {
    212         final var item = createFileItem(testFieldValueBytes, repository);
    213
    214         // Check state is as expected
    215         assertTrue(item.isInMemory(), "Initial: in memory");
    216         assertEquals(item.getSize(), testFieldValueBytes.length, "Initial: size");
    217         compareBytes("Initial", item.get(), testFieldValueBytes);
    218         testWritingToFile(item, testFieldValueBytes);
    219         item.delete();
    220     }
    221
    222     /**
    223      * Test deserialization fails when repository is not valid.
    224      *
    225      * @throws IOException Test failure.
    226      */
    227     @Test
    228     public void testInvalidRepository() throws IOException {
    229         // Create the FileItem
    230         final var testFieldValueBytes = createContentBytes(THRESHOLD);
    231         final var repository = PathUtils.getTempDirectory().resolve("file");
    232         final var item = createFileItem(testFieldValueBytes, repository);
    233         assertThrows(IOException.class, () -> deserialize(serialize(item)));
    234     }
    235
    236     /**
    237      * Test creation of a field for which the amount of data equals the configured threshold.
    238      *
    239      * @throws IOException Test failure.
    240      */
    241     @Test
    242     public void testThreshold() throws IOException {
    243         // Create the FileItem
    244         final var testFieldValueBytes = createContentBytes(THRESHOLD);
    245         testInMemoryObject(testFieldValueBytes);
    246     }
    247
    248     /**
    249      * Test serialization and deserialization when repository is not null.
    250      *
    251      * @throws IOException Test failure.
    252      */
    253     @Test
    254     public void testValidRepository() throws IOException {
    255         // Create the FileItem
    256         final var testFieldValueBytes = createContentBytes(THRESHOLD);
    257         testInMemoryObject(testFieldValueBytes, REPOSITORY);
    258     }
    259
    260     /**
    261      * Helper method to test writing item contents to a file.
    262      */
    263     private void testWritingToFile(final DiskFileItem item, final byte[] testFieldValueBytes) throws IOException {
    264         final var temp = Files.createTempFile("fileupload", null);
    265         // Note that the file exists and is initially empty;
    266         // write() must be able to handle that.
    267         item.write(temp);
    268         compareBytes("Initial", Files.readAllBytes(temp), testFieldValueBytes);
    269     }
    270 }