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 }