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 */ 017 package org.apache.commons.fileupload.disk; 018 019 import java.io.BufferedInputStream; 020 import java.io.BufferedOutputStream; 021 import java.io.ByteArrayInputStream; 022 import java.io.File; 023 import java.io.FileInputStream; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.ObjectInputStream; 028 import java.io.ObjectOutputStream; 029 import java.io.OutputStream; 030 import java.io.UnsupportedEncodingException; 031 import java.util.Map; 032 033 import org.apache.commons.fileupload.FileItem; 034 import org.apache.commons.fileupload.FileItemHeaders; 035 import org.apache.commons.fileupload.FileItemHeadersSupport; 036 import org.apache.commons.fileupload.FileUploadException; 037 import org.apache.commons.fileupload.InvalidFileNameException; 038 import org.apache.commons.fileupload.ParameterParser; 039 import org.apache.commons.fileupload.util.Streams; 040 import org.apache.commons.io.FileCleaningTracker; 041 import org.apache.commons.io.IOUtils; 042 import org.apache.commons.io.output.DeferredFileOutputStream; 043 044 045 /** 046 * <p> The default implementation of the 047 * {@link org.apache.commons.fileupload.FileItem FileItem} interface. 048 * 049 * <p> After retrieving an instance of this class from a {@link 050 * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see 051 * {@link org.apache.commons.fileupload.DiskFileUpload 052 * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may 053 * either request all contents of file at once using {@link #get()} or 054 * request an {@link java.io.InputStream InputStream} with 055 * {@link #getInputStream()} and process the file without attempting to load 056 * it into memory, which may come handy with large files. 057 * 058 * <p>Temporary files, which are created for file items, should be 059 * deleted later on. The best way to do this is using a 060 * {@link FileCleaningTracker}, which you can set on the 061 * {@link DiskFileItemFactory}. However, if you do use such a tracker, 062 * then you must consider the following: Temporary files are automatically 063 * deleted as soon as they are no longer needed. (More precisely, when the 064 * corresponding instance of {@link java.io.File} is garbage collected.) 065 * This is done by the so-called reaper thread, which is started 066 * automatically when the class {@link org.apache.commons.io.FileCleaner} 067 * is loaded. 068 * It might make sense to terminate that thread, for example, if 069 * your web application ends. See the section on "Resource cleanup" 070 * in the users guide of commons-fileupload.</p> 071 * 072 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> 073 * @author <a href="mailto:sean@informage.net">Sean Legassick</a> 074 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 075 * @author <a href="mailto:jmcnally@apache.org">John McNally</a> 076 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a> 077 * @author Sean C. Sullivan 078 * 079 * @since FileUpload 1.1 080 * 081 * @version $Id: DiskFileItem.java 963609 2010-07-13 06:56:47Z jochen $ 082 */ 083 public class DiskFileItem 084 implements FileItem, FileItemHeadersSupport { 085 086 // ----------------------------------------------------- Manifest constants 087 088 /** 089 * The UID to use when serializing this instance. 090 */ 091 private static final long serialVersionUID = 2237570099615271025L; 092 093 094 /** 095 * Default content charset to be used when no explicit charset 096 * parameter is provided by the sender. Media subtypes of the 097 * "text" type are defined to have a default charset value of 098 * "ISO-8859-1" when received via HTTP. 099 */ 100 public static final String DEFAULT_CHARSET = "ISO-8859-1"; 101 102 103 // ----------------------------------------------------------- Data members 104 105 106 /** 107 * UID used in unique file name generation. 108 */ 109 private static final String UID = 110 new java.rmi.server.UID().toString() 111 .replace(':', '_').replace('-', '_'); 112 113 /** 114 * Counter used in unique identifier generation. 115 */ 116 private static int counter = 0; 117 118 119 /** 120 * The name of the form field as provided by the browser. 121 */ 122 private String fieldName; 123 124 125 /** 126 * The content type passed by the browser, or <code>null</code> if 127 * not defined. 128 */ 129 private String contentType; 130 131 132 /** 133 * Whether or not this item is a simple form field. 134 */ 135 private boolean isFormField; 136 137 138 /** 139 * The original filename in the user's filesystem. 140 */ 141 private String fileName; 142 143 144 /** 145 * The size of the item, in bytes. This is used to cache the size when a 146 * file item is moved from its original location. 147 */ 148 private long size = -1; 149 150 151 /** 152 * The threshold above which uploads will be stored on disk. 153 */ 154 private int sizeThreshold; 155 156 157 /** 158 * The directory in which uploaded files will be stored, if stored on disk. 159 */ 160 private File repository; 161 162 163 /** 164 * Cached contents of the file. 165 */ 166 private byte[] cachedContent; 167 168 169 /** 170 * Output stream for this item. 171 */ 172 private transient DeferredFileOutputStream dfos; 173 174 /** 175 * The temporary file to use. 176 */ 177 private transient File tempFile; 178 179 /** 180 * File to allow for serialization of the content of this item. 181 */ 182 private File dfosFile; 183 184 /** 185 * The file items headers. 186 */ 187 private FileItemHeaders headers; 188 189 // ----------------------------------------------------------- Constructors 190 191 192 /** 193 * Constructs a new <code>DiskFileItem</code> instance. 194 * 195 * @param fieldName The name of the form field. 196 * @param contentType The content type passed by the browser or 197 * <code>null</code> if not specified. 198 * @param isFormField Whether or not this item is a plain form field, as 199 * opposed to a file upload. 200 * @param fileName The original filename in the user's filesystem, or 201 * <code>null</code> if not specified. 202 * @param sizeThreshold The threshold, in bytes, below which items will be 203 * retained in memory and above which they will be 204 * stored as a file. 205 * @param repository The data repository, which is the directory in 206 * which files will be created, should the item size 207 * exceed the threshold. 208 */ 209 public DiskFileItem(String fieldName, 210 String contentType, boolean isFormField, String fileName, 211 int sizeThreshold, File repository) { 212 this.fieldName = fieldName; 213 this.contentType = contentType; 214 this.isFormField = isFormField; 215 this.fileName = fileName; 216 this.sizeThreshold = sizeThreshold; 217 this.repository = repository; 218 } 219 220 221 // ------------------------------- Methods from javax.activation.DataSource 222 223 224 /** 225 * Returns an {@link java.io.InputStream InputStream} that can be 226 * used to retrieve the contents of the file. 227 * 228 * @return An {@link java.io.InputStream InputStream} that can be 229 * used to retrieve the contents of the file. 230 * 231 * @throws IOException if an error occurs. 232 */ 233 public InputStream getInputStream() 234 throws IOException { 235 if (!isInMemory()) { 236 return new FileInputStream(dfos.getFile()); 237 } 238 239 if (cachedContent == null) { 240 cachedContent = dfos.getData(); 241 } 242 return new ByteArrayInputStream(cachedContent); 243 } 244 245 246 /** 247 * Returns the content type passed by the agent or <code>null</code> if 248 * not defined. 249 * 250 * @return The content type passed by the agent or <code>null</code> if 251 * not defined. 252 */ 253 public String getContentType() { 254 return contentType; 255 } 256 257 258 /** 259 * Returns the content charset passed by the agent or <code>null</code> if 260 * not defined. 261 * 262 * @return The content charset passed by the agent or <code>null</code> if 263 * not defined. 264 */ 265 public String getCharSet() { 266 ParameterParser parser = new ParameterParser(); 267 parser.setLowerCaseNames(true); 268 // Parameter parser can handle null input 269 Map params = parser.parse(getContentType(), ';'); 270 return (String) params.get("charset"); 271 } 272 273 274 /** 275 * Returns the original filename in the client's filesystem. 276 * 277 * @return The original filename in the client's filesystem. 278 * @throws InvalidFileNameException The file name contains a NUL character, 279 * which might be an indicator of a security attack. If you intend to 280 * use the file name anyways, catch the exception and use 281 * InvalidFileNameException#getName(). 282 */ 283 public String getName() { 284 return Streams.checkFileName(fileName); 285 } 286 287 288 // ------------------------------------------------------- FileItem methods 289 290 291 /** 292 * Provides a hint as to whether or not the file contents will be read 293 * from memory. 294 * 295 * @return <code>true</code> if the file contents will be read 296 * from memory; <code>false</code> otherwise. 297 */ 298 public boolean isInMemory() { 299 if (cachedContent != null) { 300 return true; 301 } 302 return dfos.isInMemory(); 303 } 304 305 306 /** 307 * Returns the size of the file. 308 * 309 * @return The size of the file, in bytes. 310 */ 311 public long getSize() { 312 if (size >= 0) { 313 return size; 314 } else if (cachedContent != null) { 315 return cachedContent.length; 316 } else if (dfos.isInMemory()) { 317 return dfos.getData().length; 318 } else { 319 return dfos.getFile().length(); 320 } 321 } 322 323 324 /** 325 * Returns the contents of the file as an array of bytes. If the 326 * contents of the file were not yet cached in memory, they will be 327 * loaded from the disk storage and cached. 328 * 329 * @return The contents of the file as an array of bytes. 330 */ 331 public byte[] get() { 332 if (isInMemory()) { 333 if (cachedContent == null) { 334 cachedContent = dfos.getData(); 335 } 336 return cachedContent; 337 } 338 339 byte[] fileData = new byte[(int) getSize()]; 340 FileInputStream fis = null; 341 342 try { 343 fis = new FileInputStream(dfos.getFile()); 344 fis.read(fileData); 345 } catch (IOException e) { 346 fileData = null; 347 } finally { 348 if (fis != null) { 349 try { 350 fis.close(); 351 } catch (IOException e) { 352 // ignore 353 } 354 } 355 } 356 357 return fileData; 358 } 359 360 361 /** 362 * Returns the contents of the file as a String, using the specified 363 * encoding. This method uses {@link #get()} to retrieve the 364 * contents of the file. 365 * 366 * @param charset The charset to use. 367 * 368 * @return The contents of the file, as a string. 369 * 370 * @throws UnsupportedEncodingException if the requested character 371 * encoding is not available. 372 */ 373 public String getString(final String charset) 374 throws UnsupportedEncodingException { 375 return new String(get(), charset); 376 } 377 378 379 /** 380 * Returns the contents of the file as a String, using the default 381 * character encoding. This method uses {@link #get()} to retrieve the 382 * contents of the file. 383 * 384 * @return The contents of the file, as a string. 385 * 386 * @todo Consider making this method throw UnsupportedEncodingException. 387 */ 388 public String getString() { 389 byte[] rawdata = get(); 390 String charset = getCharSet(); 391 if (charset == null) { 392 charset = DEFAULT_CHARSET; 393 } 394 try { 395 return new String(rawdata, charset); 396 } catch (UnsupportedEncodingException e) { 397 return new String(rawdata); 398 } 399 } 400 401 402 /** 403 * A convenience method to write an uploaded item to disk. The client code 404 * is not concerned with whether or not the item is stored in memory, or on 405 * disk in a temporary location. They just want to write the uploaded item 406 * to a file. 407 * <p> 408 * This implementation first attempts to rename the uploaded item to the 409 * specified destination file, if the item was originally written to disk. 410 * Otherwise, the data will be copied to the specified file. 411 * <p> 412 * This method is only guaranteed to work <em>once</em>, the first time it 413 * is invoked for a particular item. This is because, in the event that the 414 * method renames a temporary file, that file will no longer be available 415 * to copy or rename again at a later time. 416 * 417 * @param file The <code>File</code> into which the uploaded item should 418 * be stored. 419 * 420 * @throws Exception if an error occurs. 421 */ 422 public void write(File file) throws Exception { 423 if (isInMemory()) { 424 FileOutputStream fout = null; 425 try { 426 fout = new FileOutputStream(file); 427 fout.write(get()); 428 } finally { 429 if (fout != null) { 430 fout.close(); 431 } 432 } 433 } else { 434 File outputFile = getStoreLocation(); 435 if (outputFile != null) { 436 // Save the length of the file 437 size = outputFile.length(); 438 /* 439 * The uploaded file is being stored on disk 440 * in a temporary location so move it to the 441 * desired file. 442 */ 443 if (!outputFile.renameTo(file)) { 444 BufferedInputStream in = null; 445 BufferedOutputStream out = null; 446 try { 447 in = new BufferedInputStream( 448 new FileInputStream(outputFile)); 449 out = new BufferedOutputStream( 450 new FileOutputStream(file)); 451 IOUtils.copy(in, out); 452 } finally { 453 if (in != null) { 454 try { 455 in.close(); 456 } catch (IOException e) { 457 // ignore 458 } 459 } 460 if (out != null) { 461 try { 462 out.close(); 463 } catch (IOException e) { 464 // ignore 465 } 466 } 467 } 468 } 469 } else { 470 /* 471 * For whatever reason we cannot write the 472 * file to disk. 473 */ 474 throw new FileUploadException( 475 "Cannot write uploaded file to disk!"); 476 } 477 } 478 } 479 480 481 /** 482 * Deletes the underlying storage for a file item, including deleting any 483 * associated temporary disk file. Although this storage will be deleted 484 * automatically when the <code>FileItem</code> instance is garbage 485 * collected, this method can be used to ensure that this is done at an 486 * earlier time, thus preserving system resources. 487 */ 488 public void delete() { 489 cachedContent = null; 490 File outputFile = getStoreLocation(); 491 if (outputFile != null && outputFile.exists()) { 492 outputFile.delete(); 493 } 494 } 495 496 497 /** 498 * Returns the name of the field in the multipart form corresponding to 499 * this file item. 500 * 501 * @return The name of the form field. 502 * 503 * @see #setFieldName(java.lang.String) 504 * 505 */ 506 public String getFieldName() { 507 return fieldName; 508 } 509 510 511 /** 512 * Sets the field name used to reference this file item. 513 * 514 * @param fieldName The name of the form field. 515 * 516 * @see #getFieldName() 517 * 518 */ 519 public void setFieldName(String fieldName) { 520 this.fieldName = fieldName; 521 } 522 523 524 /** 525 * Determines whether or not a <code>FileItem</code> instance represents 526 * a simple form field. 527 * 528 * @return <code>true</code> if the instance represents a simple form 529 * field; <code>false</code> if it represents an uploaded file. 530 * 531 * @see #setFormField(boolean) 532 * 533 */ 534 public boolean isFormField() { 535 return isFormField; 536 } 537 538 539 /** 540 * Specifies whether or not a <code>FileItem</code> instance represents 541 * a simple form field. 542 * 543 * @param state <code>true</code> if the instance represents a simple form 544 * field; <code>false</code> if it represents an uploaded file. 545 * 546 * @see #isFormField() 547 * 548 */ 549 public void setFormField(boolean state) { 550 isFormField = state; 551 } 552 553 554 /** 555 * Returns an {@link java.io.OutputStream OutputStream} that can 556 * be used for storing the contents of the file. 557 * 558 * @return An {@link java.io.OutputStream OutputStream} that can be used 559 * for storing the contensts of the file. 560 * 561 * @throws IOException if an error occurs. 562 */ 563 public OutputStream getOutputStream() 564 throws IOException { 565 if (dfos == null) { 566 File outputFile = getTempFile(); 567 dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); 568 } 569 return dfos; 570 } 571 572 573 // --------------------------------------------------------- Public methods 574 575 576 /** 577 * Returns the {@link java.io.File} object for the <code>FileItem</code>'s 578 * data's temporary location on the disk. Note that for 579 * <code>FileItem</code>s that have their data stored in memory, 580 * this method will return <code>null</code>. When handling large 581 * files, you can use {@link java.io.File#renameTo(java.io.File)} to 582 * move the file to new location without copying the data, if the 583 * source and destination locations reside within the same logical 584 * volume. 585 * 586 * @return The data file, or <code>null</code> if the data is stored in 587 * memory. 588 */ 589 public File getStoreLocation() { 590 return dfos == null ? null : dfos.getFile(); 591 } 592 593 594 // ------------------------------------------------------ Protected methods 595 596 597 /** 598 * Removes the file contents from the temporary storage. 599 */ 600 protected void finalize() { 601 File outputFile = dfos.getFile(); 602 603 if (outputFile != null && outputFile.exists()) { 604 outputFile.delete(); 605 } 606 } 607 608 609 /** 610 * Creates and returns a {@link java.io.File File} representing a uniquely 611 * named temporary file in the configured repository path. The lifetime of 612 * the file is tied to the lifetime of the <code>FileItem</code> instance; 613 * the file will be deleted when the instance is garbage collected. 614 * 615 * @return The {@link java.io.File File} to be used for temporary storage. 616 */ 617 protected File getTempFile() { 618 if (tempFile == null) { 619 File tempDir = repository; 620 if (tempDir == null) { 621 tempDir = new File(System.getProperty("java.io.tmpdir")); 622 } 623 624 String tempFileName = 625 "upload_" + UID + "_" + getUniqueId() + ".tmp"; 626 627 tempFile = new File(tempDir, tempFileName); 628 } 629 return tempFile; 630 } 631 632 633 // -------------------------------------------------------- Private methods 634 635 636 /** 637 * Returns an identifier that is unique within the class loader used to 638 * load this class, but does not have random-like apearance. 639 * 640 * @return A String with the non-random looking instance identifier. 641 */ 642 private static String getUniqueId() { 643 final int limit = 100000000; 644 int current; 645 synchronized (DiskFileItem.class) { 646 current = counter++; 647 } 648 String id = Integer.toString(current); 649 650 // If you manage to get more than 100 million of ids, you'll 651 // start getting ids longer than 8 characters. 652 if (current < limit) { 653 id = ("00000000" + id).substring(id.length()); 654 } 655 return id; 656 } 657 658 659 660 661 /** 662 * Returns a string representation of this object. 663 * 664 * @return a string representation of this object. 665 */ 666 public String toString() { 667 return "name=" + this.getName() 668 + ", StoreLocation=" 669 + String.valueOf(this.getStoreLocation()) 670 + ", size=" 671 + this.getSize() 672 + "bytes, " 673 + "isFormField=" + isFormField() 674 + ", FieldName=" 675 + this.getFieldName(); 676 } 677 678 679 // -------------------------------------------------- Serialization methods 680 681 682 /** 683 * Writes the state of this object during serialization. 684 * 685 * @param out The stream to which the state should be written. 686 * 687 * @throws IOException if an error occurs. 688 */ 689 private void writeObject(ObjectOutputStream out) throws IOException { 690 // Read the data 691 if (dfos.isInMemory()) { 692 cachedContent = get(); 693 } else { 694 cachedContent = null; 695 dfosFile = dfos.getFile(); 696 } 697 698 // write out values 699 out.defaultWriteObject(); 700 } 701 702 /** 703 * Reads the state of this object during deserialization. 704 * 705 * @param in The stream from which the state should be read. 706 * 707 * @throws IOException if an error occurs. 708 * @throws ClassNotFoundException if class cannot be found. 709 */ 710 private void readObject(ObjectInputStream in) 711 throws IOException, ClassNotFoundException { 712 // read values 713 in.defaultReadObject(); 714 715 OutputStream output = getOutputStream(); 716 if (cachedContent != null) { 717 output.write(cachedContent); 718 } else { 719 FileInputStream input = new FileInputStream(dfosFile); 720 IOUtils.copy(input, output); 721 dfosFile.delete(); 722 dfosFile = null; 723 } 724 output.close(); 725 726 cachedContent = null; 727 } 728 729 /** 730 * Returns the file item headers. 731 * @return The file items headers. 732 */ 733 public FileItemHeaders getHeaders() { 734 return headers; 735 } 736 737 /** 738 * Sets the file item headers. 739 * @param pHeaders The file items headers. 740 */ 741 public void setHeaders(FileItemHeaders pHeaders) { 742 headers = pHeaders; 743 } 744 }