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.fileupload.disk; 018 019import static java.lang.String.format; 020 021import java.io.ByteArrayInputStream; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.io.UnsupportedEncodingException; 029import java.util.Map; 030import java.util.UUID; 031import java.util.concurrent.atomic.AtomicInteger; 032 033import org.apache.commons.fileupload.FileItem; 034import org.apache.commons.fileupload.FileItemHeaders; 035import org.apache.commons.fileupload.FileUploadException; 036import org.apache.commons.fileupload.ParameterParser; 037import org.apache.commons.fileupload.util.Streams; 038import org.apache.commons.io.FileUtils; 039import org.apache.commons.io.IOUtils; 040import org.apache.commons.io.output.DeferredFileOutputStream; 041 042/** 043 * <p> The default implementation of the 044 * {@link org.apache.commons.fileupload.FileItem FileItem} interface. 045 * 046 * <p> After retrieving an instance of this class from a {@link 047 * DiskFileItemFactory} instance (see 048 * {@link org.apache.commons.fileupload.servlet.ServletFileUpload 049 * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may 050 * either request all contents of file at once using {@link #get()} or 051 * request an {@link java.io.InputStream InputStream} with 052 * {@link #getInputStream()} and process the file without attempting to load 053 * it into memory, which may come handy with large files. 054 * 055 * <p>Temporary files, which are created for file items, should be 056 * deleted later on. The best way to do this is using a 057 * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the 058 * {@link DiskFileItemFactory}. However, if you do use such a tracker, 059 * then you must consider the following: Temporary files are automatically 060 * deleted as soon as they are no longer needed. (More precisely, when the 061 * corresponding instance of {@link java.io.File} is garbage collected.) 062 * This is done by the so-called reaper thread, which is started and stopped 063 * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when 064 * there are files to be tracked. 065 * It might make sense to terminate that thread, for example, if 066 * your web application ends. See the section on "Resource cleanup" 067 * in the users guide of commons-fileupload.</p> 068 * 069 * @since FileUpload 1.1 070 */ 071public class DiskFileItem 072 implements FileItem { 073 074 // ----------------------------------------------------- Manifest constants 075 076 /** 077 * Default content charset to be used when no explicit charset 078 * parameter is provided by the sender. Media subtypes of the 079 * "text" type are defined to have a default charset value of 080 * "ISO-8859-1" when received via HTTP. 081 */ 082 public static final String DEFAULT_CHARSET = "ISO-8859-1"; 083 084 // ----------------------------------------------------------- Data members 085 086 /** 087 * UID used in unique file name generation. 088 */ 089 private static final String UID = 090 UUID.randomUUID().toString().replace('-', '_'); 091 092 /** 093 * Counter used in unique identifier generation. 094 */ 095 private static final AtomicInteger COUNTER = new AtomicInteger(0); 096 097 /** 098 * The name of the form field as provided by the browser. 099 */ 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 /** 156 * Default content charset to be used when no explicit charset 157 * parameter is provided by the sender. 158 */ 159 private String defaultCharset = DEFAULT_CHARSET; 160 161 // ----------------------------------------------------------- Constructors 162 163 /** 164 * Constructs a new <code>DiskFileItem</code> instance. 165 * 166 * @param fieldName The name of the form field. 167 * @param contentType The content type passed by the browser or 168 * <code>null</code> if not specified. 169 * @param isFormField Whether or not this item is a plain form field, as 170 * opposed to a file upload. 171 * @param fileName The original filename in the user's filesystem, or 172 * <code>null</code> if not specified. 173 * @param sizeThreshold The threshold, in bytes, below which items will be 174 * retained in memory and above which they will be 175 * stored as a file. 176 * @param repository The data repository, which is the directory in 177 * which files will be created, should the item size 178 * exceed the threshold. 179 */ 180 public DiskFileItem(String fieldName, 181 String contentType, boolean isFormField, String fileName, 182 int sizeThreshold, File repository) { 183 this.fieldName = fieldName; 184 this.contentType = contentType; 185 this.isFormField = isFormField; 186 this.fileName = fileName; 187 this.sizeThreshold = sizeThreshold; 188 this.repository = repository; 189 } 190 191 // ------------------------------- Methods from javax.activation.DataSource 192 193 /** 194 * Returns an {@link java.io.InputStream InputStream} that can be 195 * used to retrieve the contents of the file. 196 * 197 * @return An {@link java.io.InputStream InputStream} that can be 198 * used to retrieve the contents of the file. 199 * 200 * @throws IOException if an error occurs. 201 */ 202 @Override 203 public InputStream getInputStream() 204 throws IOException { 205 if (!isInMemory()) { 206 return new FileInputStream(dfos.getFile()); 207 } 208 209 if (cachedContent == null) { 210 cachedContent = dfos.getData(); 211 } 212 return new ByteArrayInputStream(cachedContent); 213 } 214 215 /** 216 * Returns the content type passed by the agent or <code>null</code> if 217 * not defined. 218 * 219 * @return The content type passed by the agent or <code>null</code> if 220 * not defined. 221 */ 222 @Override 223 public String getContentType() { 224 return contentType; 225 } 226 227 /** 228 * Returns the content charset passed by the agent or <code>null</code> if 229 * not defined. 230 * 231 * @return The content charset passed by the agent or <code>null</code> if 232 * not defined. 233 */ 234 public String getCharSet() { 235 ParameterParser parser = new ParameterParser(); 236 parser.setLowerCaseNames(true); 237 // Parameter parser can handle null input 238 Map<String, String> params = parser.parse(getContentType(), ';'); 239 return params.get("charset"); 240 } 241 242 /** 243 * Returns the original filename in the client's filesystem. 244 * 245 * @return The original filename in the client's filesystem. 246 * @throws org.apache.commons.fileupload.InvalidFileNameException The file name contains a NUL character, 247 * which might be an indicator of a security attack. If you intend to 248 * use the file name anyways, catch the exception and use 249 * {@link org.apache.commons.fileupload.InvalidFileNameException#getName()}. 250 */ 251 @Override 252 public String getName() { 253 return Streams.checkFileName(fileName); 254 } 255 256 // ------------------------------------------------------- FileItem methods 257 258 /** 259 * Provides a hint as to whether or not the file contents will be read 260 * from memory. 261 * 262 * @return <code>true</code> if the file contents will be read 263 * from memory; <code>false</code> otherwise. 264 */ 265 @Override 266 public boolean isInMemory() { 267 if (cachedContent != null) { 268 return true; 269 } 270 return dfos.isInMemory(); 271 } 272 273 /** 274 * Returns the size of the file. 275 * 276 * @return The size of the file, in bytes. 277 */ 278 @Override 279 public long getSize() { 280 if (size >= 0) { 281 return size; 282 } else if (cachedContent != null) { 283 return cachedContent.length; 284 } else if (dfos.isInMemory()) { 285 return dfos.getData().length; 286 } else { 287 return dfos.getFile().length(); 288 } 289 } 290 291 /** 292 * Returns the contents of the file as an array of bytes. If the 293 * contents of the file were not yet cached in memory, they will be 294 * loaded from the disk storage and cached. 295 * 296 * @return The contents of the file as an array of bytes 297 * or {@code null} if the data cannot be read 298 */ 299 @Override 300 public byte[] get() { 301 if (isInMemory()) { 302 if (cachedContent == null && dfos != null) { 303 cachedContent = dfos.getData(); 304 } 305 return cachedContent; 306 } 307 308 byte[] fileData = new byte[(int) getSize()]; 309 InputStream fis = null; 310 311 try { 312 fis = new FileInputStream(dfos.getFile()); 313 IOUtils.readFully(fis, fileData); 314 } catch (IOException e) { 315 fileData = null; 316 } finally { 317 IOUtils.closeQuietly(fis); 318 } 319 320 return fileData; 321 } 322 323 /** 324 * Returns the contents of the file as a String, using the specified 325 * encoding. This method uses {@link #get()} to retrieve the 326 * contents of the file. 327 * 328 * @param charset The charset to use. 329 * 330 * @return The contents of the file, as a string. 331 * 332 * @throws UnsupportedEncodingException if the requested character 333 * encoding is not available. 334 */ 335 @Override 336 public String getString(final String charset) 337 throws UnsupportedEncodingException { 338 return new String(get(), charset); 339 } 340 341 /** 342 * Returns the contents of the file as a String, using the default 343 * character encoding. This method uses {@link #get()} to retrieve the 344 * contents of the file. 345 * 346 * <b>TODO</b> Consider making this method throw UnsupportedEncodingException. 347 * 348 * @return The contents of the file, as a string. 349 */ 350 @Override 351 public String getString() { 352 byte[] rawdata = get(); 353 String charset = getCharSet(); 354 if (charset == null) { 355 charset = defaultCharset; 356 } 357 try { 358 return new String(rawdata, charset); 359 } catch (UnsupportedEncodingException e) { 360 return new String(rawdata); 361 } 362 } 363 364 /** 365 * A convenience method to write an uploaded item to disk. The client code 366 * is not concerned with whether or not the item is stored in memory, or on 367 * disk in a temporary location. They just want to write the uploaded item 368 * to a file. 369 * <p> 370 * This implementation first attempts to rename the uploaded item to the 371 * specified destination file, if the item was originally written to disk. 372 * Otherwise, the data will be copied to the specified file. 373 * <p> 374 * This method is only guaranteed to work <em>once</em>, the first time it 375 * is invoked for a particular item. This is because, in the event that the 376 * method renames a temporary file, that file will no longer be available 377 * to copy or rename again at a later time. 378 * 379 * @param file The <code>File</code> into which the uploaded item should 380 * be stored. 381 * 382 * @throws Exception if an error occurs. 383 */ 384 @Override 385 public void write(File file) throws Exception { 386 if (isInMemory()) { 387 FileOutputStream fout = null; 388 try { 389 fout = new FileOutputStream(file); 390 fout.write(get()); 391 fout.close(); 392 } finally { 393 IOUtils.closeQuietly(fout); 394 } 395 } else { 396 File outputFile = getStoreLocation(); 397 if (outputFile != null) { 398 // Save the length of the file 399 size = outputFile.length(); 400 /* 401 * The uploaded file is being stored on disk 402 * in a temporary location so move it to the 403 * desired file. 404 */ 405 if (file.exists()) { 406 file.delete(); 407 } 408 FileUtils.moveFile(outputFile, file); 409 } else { 410 /* 411 * For whatever reason we cannot write the 412 * file to disk. 413 */ 414 throw new FileUploadException( 415 "Cannot write uploaded file to disk!"); 416 } 417 } 418 } 419 420 /** 421 * Deletes the underlying storage for a file item, including deleting any 422 * associated temporary disk file. Although this storage will be deleted 423 * automatically when the <code>FileItem</code> instance is garbage 424 * collected, this method can be used to ensure that this is done at an 425 * earlier time, thus preserving system resources. 426 */ 427 @Override 428 public void delete() { 429 cachedContent = null; 430 File outputFile = getStoreLocation(); 431 if (outputFile != null && !isInMemory() && outputFile.exists()) { 432 outputFile.delete(); 433 } 434 } 435 436 /** 437 * Returns the name of the field in the multipart form corresponding to 438 * this file item. 439 * 440 * @return The name of the form field. 441 * 442 * @see #setFieldName(java.lang.String) 443 * 444 */ 445 @Override 446 public String getFieldName() { 447 return fieldName; 448 } 449 450 /** 451 * Sets the field name used to reference this file item. 452 * 453 * @param fieldName The name of the form field. 454 * 455 * @see #getFieldName() 456 * 457 */ 458 @Override 459 public void setFieldName(String fieldName) { 460 this.fieldName = fieldName; 461 } 462 463 /** 464 * Determines whether or not a <code>FileItem</code> instance represents 465 * a simple form field. 466 * 467 * @return <code>true</code> if the instance represents a simple form 468 * field; <code>false</code> if it represents an uploaded file. 469 * 470 * @see #setFormField(boolean) 471 * 472 */ 473 @Override 474 public boolean isFormField() { 475 return isFormField; 476 } 477 478 /** 479 * Specifies whether or not a <code>FileItem</code> instance represents 480 * a simple form field. 481 * 482 * @param state <code>true</code> if the instance represents a simple form 483 * field; <code>false</code> if it represents an uploaded file. 484 * 485 * @see #isFormField() 486 * 487 */ 488 @Override 489 public void setFormField(boolean state) { 490 isFormField = state; 491 } 492 493 /** 494 * Returns an {@link java.io.OutputStream OutputStream} that can 495 * be used for storing the contents of the file. 496 * 497 * @return An {@link java.io.OutputStream OutputStream} that can be used 498 * for storing the contents of the file. 499 * 500 * @throws IOException if an error occurs. 501 */ 502 @Override 503 public OutputStream getOutputStream() 504 throws IOException { 505 if (dfos == null) { 506 File outputFile = getTempFile(); 507 dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); 508 } 509 return dfos; 510 } 511 512 // --------------------------------------------------------- Public methods 513 514 /** 515 * Returns the {@link java.io.File} object for the <code>FileItem</code>'s 516 * data's temporary location on the disk. Note that for 517 * <code>FileItem</code>s that have their data stored in memory, 518 * this method will return <code>null</code>. When handling large 519 * files, you can use {@link java.io.File#renameTo(java.io.File)} to 520 * move the file to new location without copying the data, if the 521 * source and destination locations reside within the same logical 522 * volume. 523 * 524 * @return The data file, or <code>null</code> if the data is stored in 525 * memory. 526 */ 527 public File getStoreLocation() { 528 if (dfos == null) { 529 return null; 530 } 531 if (isInMemory()) { 532 return null; 533 } 534 return dfos.getFile(); 535 } 536 537 // ------------------------------------------------------ Protected methods 538 539 /** 540 * Removes the file contents from the temporary storage. 541 */ 542 @Override 543 protected void finalize() { 544 if (dfos == null || dfos.isInMemory()) { 545 return; 546 } 547 File outputFile = dfos.getFile(); 548 549 if (outputFile != null && outputFile.exists()) { 550 outputFile.delete(); 551 } 552 } 553 554 /** 555 * Creates and returns a {@link java.io.File File} representing a uniquely 556 * named temporary file in the configured repository path. The lifetime of 557 * the file is tied to the lifetime of the <code>FileItem</code> instance; 558 * the file will be deleted when the instance is garbage collected. 559 * <p> 560 * <b>Note: Subclasses that override this method must ensure that they return the 561 * same File each time.</b> 562 * 563 * @return The {@link java.io.File File} to be used for temporary storage. 564 */ 565 protected File getTempFile() { 566 if (tempFile == null) { 567 File tempDir = repository; 568 if (tempDir == null) { 569 tempDir = new File(System.getProperty("java.io.tmpdir")); 570 } 571 572 String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId()); 573 574 tempFile = new File(tempDir, tempFileName); 575 } 576 return tempFile; 577 } 578 579 // -------------------------------------------------------- Private methods 580 581 /** 582 * Returns an identifier that is unique within the class loader used to 583 * load this class, but does not have random-like appearance. 584 * 585 * @return A String with the non-random looking instance identifier. 586 */ 587 private static String getUniqueId() { 588 final int limit = 100000000; 589 int current = COUNTER.getAndIncrement(); 590 String id = Integer.toString(current); 591 592 // If you manage to get more than 100 million of ids, you'll 593 // start getting ids longer than 8 characters. 594 if (current < limit) { 595 id = ("00000000" + id).substring(id.length()); 596 } 597 return id; 598 } 599 600 /** 601 * Returns a string representation of this object. 602 * 603 * @return a string representation of this object. 604 */ 605 @Override 606 public String toString() { 607 return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s", 608 getName(), getStoreLocation(), Long.valueOf(getSize()), 609 Boolean.valueOf(isFormField()), getFieldName()); 610 } 611 612 /** 613 * Returns the file item headers. 614 * @return The file items headers. 615 */ 616 @Override 617 public FileItemHeaders getHeaders() { 618 return headers; 619 } 620 621 /** 622 * Sets the file item headers. 623 * @param pHeaders The file items headers. 624 */ 625 @Override 626 public void setHeaders(FileItemHeaders pHeaders) { 627 headers = pHeaders; 628 } 629 630 /** 631 * Returns the default charset for use when no explicit charset 632 * parameter is provided by the sender. 633 * @return the default charset 634 */ 635 public String getDefaultCharset() { 636 return defaultCharset; 637 } 638 639 /** 640 * Sets the default charset for use when no explicit charset 641 * parameter is provided by the sender. 642 * @param charset the default charset 643 */ 644 public void setDefaultCharset(String charset) { 645 defaultCharset = charset; 646 } 647}