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.disk; 18 19 import static java.lang.String.format; 20 21 import java.io.ByteArrayInputStream; 22 import java.io.File; 23 import java.io.FileInputStream; 24 import java.io.FileOutputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.io.UnsupportedEncodingException; 29 import java.util.Map; 30 import java.util.UUID; 31 import java.util.concurrent.atomic.AtomicInteger; 32 33 import org.apache.commons.fileupload.FileItem; 34 import org.apache.commons.fileupload.FileItemHeaders; 35 import org.apache.commons.fileupload.FileUploadException; 36 import org.apache.commons.fileupload.ParameterParser; 37 import org.apache.commons.fileupload.util.Streams; 38 import org.apache.commons.io.FileUtils; 39 import org.apache.commons.io.IOUtils; 40 import org.apache.commons.io.output.DeferredFileOutputStream; 41 42 /** 43 * <p> The default implementation of the 44 * {@link org.apache.commons.fileupload.FileItem FileItem} interface. 45 * 46 * <p> After retrieving an instance of this class from a {@link 47 * DiskFileItemFactory} instance (see 48 * {@link org.apache.commons.fileupload.servlet.ServletFileUpload 49 * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may 50 * either request all contents of file at once using {@link #get()} or 51 * request an {@link java.io.InputStream InputStream} with 52 * {@link #getInputStream()} and process the file without attempting to load 53 * it into memory, which may come handy with large files. 54 * 55 * <p>Temporary files, which are created for file items, should be 56 * deleted later on. The best way to do this is using a 57 * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the 58 * {@link DiskFileItemFactory}. However, if you do use such a tracker, 59 * then you must consider the following: Temporary files are automatically 60 * deleted as soon as they are no longer needed. (More precisely, when the 61 * corresponding instance of {@link java.io.File} is garbage collected.) 62 * This is done by the so-called reaper thread, which is started and stopped 63 * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when 64 * there are files to be tracked. 65 * It might make sense to terminate that thread, for example, if 66 * your web application ends. See the section on "Resource cleanup" 67 * in the users guide of commons-fileupload.</p> 68 * 69 * @since FileUpload 1.1 70 */ 71 public class DiskFileItem 72 implements FileItem { 73 74 // ----------------------------------------------------- Manifest constants 75 76 /** 77 * Default content charset to be used when no explicit charset 78 * parameter is provided by the sender. Media subtypes of the 79 * "text" type are defined to have a default charset value of 80 * "ISO-8859-1" when received via HTTP. 81 */ 82 public static final String DEFAULT_CHARSET = "ISO-8859-1"; 83 84 // ----------------------------------------------------------- Data members 85 86 /** 87 * UID used in unique file name generation. 88 */ 89 private static final String UID = 90 UUID.randomUUID().toString().replace('-', '_'); 91 92 /** 93 * Counter used in unique identifier generation. 94 */ 95 private static final AtomicInteger COUNTER = new AtomicInteger(0); 96 97 /** 98 * The name of the form field as provided by the browser. 99 */ 100 private String fieldName; 101 102 /** 103 * The content type passed by the browser, or <code>null</code> if 104 * not defined. 105 */ 106 private final String contentType; 107 108 /** 109 * Whether or not this item is a simple form field. 110 */ 111 private boolean isFormField; 112 113 /** 114 * The original filename in the user's filesystem. 115 */ 116 private final String fileName; 117 118 /** 119 * The size of the item, in bytes. This is used to cache the size when a 120 * file item is moved from its original location. 121 */ 122 private long size = -1; 123 124 125 /** 126 * The threshold above which uploads will be stored on disk. 127 */ 128 private final int sizeThreshold; 129 130 /** 131 * The directory in which uploaded files will be stored, if stored on disk. 132 */ 133 private final File repository; 134 135 /** 136 * Cached contents of the file. 137 */ 138 private byte[] cachedContent; 139 140 /** 141 * Output stream for this item. 142 */ 143 private transient DeferredFileOutputStream dfos; 144 145 /** 146 * The temporary file to use. 147 */ 148 private transient File tempFile; 149 150 /** 151 * The file items headers. 152 */ 153 private FileItemHeaders headers; 154 155 // ----------------------------------------------------------- Constructors 156 157 /** 158 * Constructs a new <code>DiskFileItem</code> instance. 159 * 160 * @param fieldName The name of the form field. 161 * @param contentType The content type passed by the browser or 162 * <code>null</code> if not specified. 163 * @param isFormField Whether or not this item is a plain form field, as 164 * opposed to a file upload. 165 * @param fileName The original filename in the user's filesystem, or 166 * <code>null</code> if not specified. 167 * @param sizeThreshold The threshold, in bytes, below which items will be 168 * retained in memory and above which they will be 169 * stored as a file. 170 * @param repository The data repository, which is the directory in 171 * which files will be created, should the item size 172 * exceed the threshold. 173 */ 174 public DiskFileItem(String fieldName, 175 String contentType, boolean isFormField, String fileName, 176 int sizeThreshold, File repository) { 177 this.fieldName = fieldName; 178 this.contentType = contentType; 179 this.isFormField = isFormField; 180 this.fileName = fileName; 181 this.sizeThreshold = sizeThreshold; 182 this.repository = repository; 183 } 184 185 // ------------------------------- Methods from javax.activation.DataSource 186 187 /** 188 * Returns an {@link java.io.InputStream InputStream} that can be 189 * used to retrieve the contents of the file. 190 * 191 * @return An {@link java.io.InputStream InputStream} that can be 192 * used to retrieve the contents of the file. 193 * 194 * @throws IOException if an error occurs. 195 */ 196 public InputStream getInputStream() 197 throws IOException { 198 if (!isInMemory()) { 199 return new FileInputStream(dfos.getFile()); 200 } 201 202 if (cachedContent == null) { 203 cachedContent = dfos.getData(); 204 } 205 return new ByteArrayInputStream(cachedContent); 206 } 207 208 /** 209 * Returns the content type passed by the agent or <code>null</code> if 210 * not defined. 211 * 212 * @return The content type passed by the agent or <code>null</code> if 213 * not defined. 214 */ 215 public String getContentType() { 216 return contentType; 217 } 218 219 /** 220 * Returns the content charset passed by the agent or <code>null</code> if 221 * not defined. 222 * 223 * @return The content charset passed by the agent or <code>null</code> if 224 * not defined. 225 */ 226 public String getCharSet() { 227 ParameterParser parser = new ParameterParser(); 228 parser.setLowerCaseNames(true); 229 // Parameter parser can handle null input 230 Map<String, String> params = parser.parse(getContentType(), ';'); 231 return params.get("charset"); 232 } 233 234 /** 235 * Returns the original filename in the client's filesystem. 236 * 237 * @return The original filename in the client's filesystem. 238 * @throws org.apache.commons.fileupload.InvalidFileNameException The file name contains a NUL character, 239 * which might be an indicator of a security attack. If you intend to 240 * use the file name anyways, catch the exception and use 241 * {@link org.apache.commons.fileupload.InvalidFileNameException#getName()}. 242 */ 243 public String getName() { 244 return Streams.checkFileName(fileName); 245 } 246 247 // ------------------------------------------------------- FileItem methods 248 249 /** 250 * Provides a hint as to whether or not the file contents will be read 251 * from memory. 252 * 253 * @return <code>true</code> if the file contents will be read 254 * from memory; <code>false</code> otherwise. 255 */ 256 public boolean isInMemory() { 257 if (cachedContent != null) { 258 return true; 259 } 260 return dfos.isInMemory(); 261 } 262 263 /** 264 * Returns the size of the file. 265 * 266 * @return The size of the file, in bytes. 267 */ 268 public long getSize() { 269 if (size >= 0) { 270 return size; 271 } else if (cachedContent != null) { 272 return cachedContent.length; 273 } else if (dfos.isInMemory()) { 274 return dfos.getData().length; 275 } else { 276 return dfos.getFile().length(); 277 } 278 } 279 280 /** 281 * Returns the contents of the file as an array of bytes. If the 282 * contents of the file were not yet cached in memory, they will be 283 * loaded from the disk storage and cached. 284 * 285 * @return The contents of the file as an array of bytes 286 * or {@code null} if the data cannot be read 287 */ 288 public byte[] get() { 289 if (isInMemory()) { 290 if (cachedContent == null && dfos != null) { 291 cachedContent = dfos.getData(); 292 } 293 return cachedContent; 294 } 295 296 byte[] fileData = new byte[(int) getSize()]; 297 InputStream fis = null; 298 299 try { 300 fis = new FileInputStream(dfos.getFile()); 301 IOUtils.readFully(fis, fileData); 302 } catch (IOException e) { 303 fileData = null; 304 } finally { 305 IOUtils.closeQuietly(fis); 306 } 307 308 return fileData; 309 } 310 311 /** 312 * Returns the contents of the file as a String, using the specified 313 * encoding. This method uses {@link #get()} to retrieve the 314 * contents of the file. 315 * 316 * @param charset The charset to use. 317 * 318 * @return The contents of the file, as a string. 319 * 320 * @throws UnsupportedEncodingException if the requested character 321 * encoding is not available. 322 */ 323 public String getString(final String charset) 324 throws UnsupportedEncodingException { 325 return new String(get(), charset); 326 } 327 328 /** 329 * Returns the contents of the file as a String, using the default 330 * character encoding. This method uses {@link #get()} to retrieve the 331 * contents of the file. 332 * 333 * <b>TODO</b> Consider making this method throw UnsupportedEncodingException. 334 * 335 * @return The contents of the file, as a string. 336 */ 337 public String getString() { 338 byte[] rawdata = get(); 339 String charset = getCharSet(); 340 if (charset == null) { 341 charset = DEFAULT_CHARSET; 342 } 343 try { 344 return new String(rawdata, charset); 345 } catch (UnsupportedEncodingException e) { 346 return new String(rawdata); 347 } 348 } 349 350 /** 351 * A convenience method to write an uploaded item to disk. The client code 352 * is not concerned with whether or not the item is stored in memory, or on 353 * disk in a temporary location. They just want to write the uploaded item 354 * to a file. 355 * <p> 356 * This implementation first attempts to rename the uploaded item to the 357 * specified destination file, if the item was originally written to disk. 358 * Otherwise, the data will be copied to the specified file. 359 * <p> 360 * This method is only guaranteed to work <em>once</em>, the first time it 361 * is invoked for a particular item. This is because, in the event that the 362 * method renames a temporary file, that file will no longer be available 363 * to copy or rename again at a later time. 364 * 365 * @param file The <code>File</code> into which the uploaded item should 366 * be stored. 367 * 368 * @throws Exception if an error occurs. 369 */ 370 public void write(File file) throws Exception { 371 if (isInMemory()) { 372 FileOutputStream fout = null; 373 try { 374 fout = new FileOutputStream(file); 375 fout.write(get()); 376 fout.close(); 377 } finally { 378 IOUtils.closeQuietly(fout); 379 } 380 } else { 381 File outputFile = getStoreLocation(); 382 if (outputFile != null) { 383 // Save the length of the file 384 size = outputFile.length(); 385 /* 386 * The uploaded file is being stored on disk 387 * in a temporary location so move it to the 388 * desired file. 389 */ 390 FileUtils.moveFile(outputFile, file); 391 } else { 392 /* 393 * For whatever reason we cannot write the 394 * file to disk. 395 */ 396 throw new FileUploadException( 397 "Cannot write uploaded file to disk!"); 398 } 399 } 400 } 401 402 /** 403 * Deletes the underlying storage for a file item, including deleting any 404 * associated temporary disk file. Although this storage will be deleted 405 * automatically when the <code>FileItem</code> instance is garbage 406 * collected, this method can be used to ensure that this is done at an 407 * earlier time, thus preserving system resources. 408 */ 409 public void delete() { 410 cachedContent = null; 411 File outputFile = getStoreLocation(); 412 if (outputFile != null && outputFile.exists()) { 413 outputFile.delete(); 414 } 415 } 416 417 /** 418 * Returns the name of the field in the multipart form corresponding to 419 * this file item. 420 * 421 * @return The name of the form field. 422 * 423 * @see #setFieldName(java.lang.String) 424 * 425 */ 426 public String getFieldName() { 427 return fieldName; 428 } 429 430 /** 431 * Sets the field name used to reference this file item. 432 * 433 * @param fieldName The name of the form field. 434 * 435 * @see #getFieldName() 436 * 437 */ 438 public void setFieldName(String fieldName) { 439 this.fieldName = fieldName; 440 } 441 442 /** 443 * Determines whether or not a <code>FileItem</code> instance represents 444 * a simple form field. 445 * 446 * @return <code>true</code> if the instance represents a simple form 447 * field; <code>false</code> if it represents an uploaded file. 448 * 449 * @see #setFormField(boolean) 450 * 451 */ 452 public boolean isFormField() { 453 return isFormField; 454 } 455 456 /** 457 * Specifies whether or not a <code>FileItem</code> instance represents 458 * a simple form field. 459 * 460 * @param state <code>true</code> if the instance represents a simple form 461 * field; <code>false</code> if it represents an uploaded file. 462 * 463 * @see #isFormField() 464 * 465 */ 466 public void setFormField(boolean state) { 467 isFormField = state; 468 } 469 470 /** 471 * Returns an {@link java.io.OutputStream OutputStream} that can 472 * be used for storing the contents of the file. 473 * 474 * @return An {@link java.io.OutputStream OutputStream} that can be used 475 * for storing the contents of the file. 476 * 477 * @throws IOException if an error occurs. 478 */ 479 public OutputStream getOutputStream() 480 throws IOException { 481 if (dfos == null) { 482 File outputFile = getTempFile(); 483 dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); 484 } 485 return dfos; 486 } 487 488 // --------------------------------------------------------- Public methods 489 490 /** 491 * Returns the {@link java.io.File} object for the <code>FileItem</code>'s 492 * data's temporary location on the disk. Note that for 493 * <code>FileItem</code>s that have their data stored in memory, 494 * this method will return <code>null</code>. When handling large 495 * files, you can use {@link java.io.File#renameTo(java.io.File)} to 496 * move the file to new location without copying the data, if the 497 * source and destination locations reside within the same logical 498 * volume. 499 * 500 * @return The data file, or <code>null</code> if the data is stored in 501 * memory. 502 */ 503 public File getStoreLocation() { 504 if (dfos == null) { 505 return null; 506 } 507 if (isInMemory()) { 508 return null; 509 } 510 return dfos.getFile(); 511 } 512 513 // ------------------------------------------------------ Protected methods 514 515 /** 516 * Removes the file contents from the temporary storage. 517 */ 518 @Override 519 protected void finalize() { 520 if (dfos == null) { 521 return; 522 } 523 File outputFile = dfos.getFile(); 524 525 if (outputFile != null && outputFile.exists()) { 526 outputFile.delete(); 527 } 528 } 529 530 /** 531 * Creates and returns a {@link java.io.File File} representing a uniquely 532 * named temporary file in the configured repository path. The lifetime of 533 * the file is tied to the lifetime of the <code>FileItem</code> instance; 534 * the file will be deleted when the instance is garbage collected. 535 * <p> 536 * <b>Note: Subclasses that override this method must ensure that they return the 537 * same File each time.</b> 538 * 539 * @return The {@link java.io.File File} to be used for temporary storage. 540 */ 541 protected File getTempFile() { 542 if (tempFile == null) { 543 File tempDir = repository; 544 if (tempDir == null) { 545 tempDir = new File(System.getProperty("java.io.tmpdir")); 546 } 547 548 String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId()); 549 550 tempFile = new File(tempDir, tempFileName); 551 } 552 return tempFile; 553 } 554 555 // -------------------------------------------------------- Private methods 556 557 /** 558 * Returns an identifier that is unique within the class loader used to 559 * load this class, but does not have random-like appearance. 560 * 561 * @return A String with the non-random looking instance identifier. 562 */ 563 private static String getUniqueId() { 564 final int limit = 100000000; 565 int current = COUNTER.getAndIncrement(); 566 String id = Integer.toString(current); 567 568 // If you manage to get more than 100 million of ids, you'll 569 // start getting ids longer than 8 characters. 570 if (current < limit) { 571 id = ("00000000" + id).substring(id.length()); 572 } 573 return id; 574 } 575 576 /** 577 * Returns a string representation of this object. 578 * 579 * @return a string representation of this object. 580 */ 581 @Override 582 public String toString() { 583 return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s", 584 getName(), getStoreLocation(), Long.valueOf(getSize()), 585 Boolean.valueOf(isFormField()), getFieldName()); 586 } 587 588 /** 589 * Returns the file item headers. 590 * @return The file items headers. 591 */ 592 public FileItemHeaders getHeaders() { 593 return headers; 594 } 595 596 /** 597 * Sets the file item headers. 598 * @param pHeaders The file items headers. 599 */ 600 public void setHeaders(FileItemHeaders pHeaders) { 601 headers = pHeaders; 602 } 603 604 }