001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.fileupload2.core; 018 019import static org.junit.jupiter.api.Assertions.assertEquals; 020import static org.junit.jupiter.api.Assertions.assertFalse; 021import static org.junit.jupiter.api.Assertions.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertThrows; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.ObjectOutputStream; 028import java.nio.file.FileVisitResult; 029import java.nio.file.Files; 030import java.nio.file.InvalidPathException; 031import java.nio.file.Path; 032import java.nio.file.attribute.BasicFileAttributes; 033 034import org.apache.commons.io.file.PathUtils; 035import org.apache.commons.io.file.SimplePathVisitor; 036import org.apache.commons.lang3.SerializationUtils; 037import org.junit.jupiter.api.AfterEach; 038import org.junit.jupiter.api.BeforeEach; 039import org.junit.jupiter.api.Test; 040 041/** 042 * Serialization Unit tests for {@link DiskFileItem}. 043 */ 044public class DiskFileItemSerializeTest { 045 046 /** 047 * Use a private repository to catch any files left over by tests. 048 */ 049 private static final Path REPOSITORY = PathUtils.getTempDirectory().resolve("DiskFileItemRepo"); 050 051 /** 052 * Content type for regular form items. 053 */ 054 private static final String TEXT_CONTENT_TYPE = "text/plain"; 055 056 /** 057 * Very low threshold for testing memory versus disk options. 058 */ 059 private static final int THRESHOLD = 16; 060 061 /** 062 * Compare content bytes. 063 */ 064 private void compareBytes(final String text, final byte[] origBytes, final byte[] newBytes) { 065 assertNotNull(origBytes, "origBytes must not be null"); 066 assertNotNull(newBytes, "newBytes must not be null"); 067 assertEquals(origBytes.length, newBytes.length, text + " byte[] length"); 068 for (var i = 0; i < origBytes.length; i++) { 069 assertEquals(origBytes[i], newBytes[i], text + " byte[" + i + "]"); 070 } 071 } 072 073 /** 074 * Create content bytes of a specified size. 075 */ 076 private byte[] createContentBytes(final int size) { 077 final var buffer = new StringBuilder(size); 078 byte count = 0; 079 for (var i = 0; i < size; i++) { 080 buffer.append(count + ""); 081 count++; 082 if (count > 9) { 083 count = 0; 084 } 085 } 086 return buffer.toString().getBytes(); 087 } 088 089 /** 090 * Create a FileItem with the specfied content bytes. 091 */ 092 private DiskFileItem createFileItem(final byte[] contentBytes) throws IOException { 093 return createFileItem(contentBytes, REPOSITORY); 094 } 095 096 /** 097 * Create a FileItem with the specfied content bytes and repository. 098 */ 099 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}