Coverage Report - org.apache.commons.fileupload.disk.DiskFileItem
 
Classes in this File Line Coverage Branch Coverage Complexity
DiskFileItem
69%
78/112
60%
28/46
2,5
 
 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  1
             UUID.randomUUID().toString().replace('-', '_');
 91  
 
 92  
     /**
 93  
      * Counter used in unique identifier generation.
 94  
      */
 95  1
     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  2173
     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  2173
             int sizeThreshold, File repository) {
 177  2173
         this.fieldName = fieldName;
 178  2173
         this.contentType = contentType;
 179  2173
         this.isFormField = isFormField;
 180  2173
         this.fileName = fileName;
 181  2173
         this.sizeThreshold = sizeThreshold;
 182  2173
         this.repository = repository;
 183  2173
     }
 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  0
         if (!isInMemory()) {
 199  0
             return new FileInputStream(dfos.getFile());
 200  
         }
 201  
 
 202  0
         if (cachedContent == null) {
 203  0
             cachedContent = dfos.getData();
 204  
         }
 205  0
         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  41
         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  33
         ParameterParser parser = new ParameterParser();
 228  33
         parser.setLowerCaseNames(true);
 229  
         // Parameter parser can handle null input
 230  33
         Map<String, String> params = parser.parse(getContentType(), ';');
 231  33
         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  19
         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  2165
         if (cachedContent != null) {
 258  1
             return true;
 259  
         }
 260  2164
         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  536
         if (size >= 0) {
 270  0
             return size;
 271  536
         } else if (cachedContent != null) {
 272  0
             return cachedContent.length;
 273  536
         } else if (dfos.isInMemory()) {
 274  4
             return dfos.getData().length;
 275  
         } else {
 276  532
             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  1449
         if (isInMemory()) {
 290  920
             if (cachedContent == null && dfos != null) {
 291  919
                 cachedContent = dfos.getData();
 292  
             }
 293  920
             return cachedContent;
 294  
         }
 295  
 
 296  529
         byte[] fileData = new byte[(int) getSize()];
 297  529
         InputStream fis = null;
 298  
 
 299  
         try {
 300  529
             fis = new FileInputStream(dfos.getFile());
 301  529
             IOUtils.readFully(fis, fileData);
 302  0
         } catch (IOException e) {
 303  0
             fileData = null;
 304  
         } finally {
 305  529
             IOUtils.closeQuietly(fis);
 306  529
         }
 307  
 
 308  529
         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  0
         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  33
         byte[] rawdata = get();
 339  33
         String charset = getCharSet();
 340  33
         if (charset == null) {
 341  33
             charset = DEFAULT_CHARSET;
 342  
         }
 343  
         try {
 344  33
             return new String(rawdata, charset);
 345  0
         } catch (UnsupportedEncodingException e) {
 346  0
             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  0
         if (isInMemory()) {
 372  0
             FileOutputStream fout = null;
 373  
             try {
 374  0
                 fout = new FileOutputStream(file);
 375  0
                 fout.write(get());
 376  0
                 fout.close();
 377  
             } finally {
 378  0
                 IOUtils.closeQuietly(fout);
 379  0
             }
 380  0
         } else {
 381  0
             File outputFile = getStoreLocation();
 382  0
             if (outputFile != null) {
 383  
                 // Save the length of the file
 384  0
                 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  0
                 FileUtils.moveFile(outputFile, file);
 391  
             } else {
 392  
                 /*
 393  
                  * For whatever reason we cannot write the
 394  
                  * file to disk.
 395  
                  */
 396  0
                 throw new FileUploadException(
 397  
                     "Cannot write uploaded file to disk!");
 398  
             }
 399  
         }
 400  0
     }
 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  707
         cachedContent = null;
 411  707
         File outputFile = getStoreLocation();
 412  707
         if (outputFile != null && outputFile.exists()) {
 413  265
             outputFile.delete();
 414  
         }
 415  707
     }
 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  1445
         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  0
         this.fieldName = fieldName;
 440  0
     }
 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  32
         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  0
         isFormField = state;
 468  0
     }
 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  2171
         if (dfos == null) {
 482  2171
             File outputFile = getTempFile();
 483  2171
             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
 484  
         }
 485  2171
         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  709
         if (dfos == null) {
 505  0
             return null;
 506  
         }
 507  709
         if (isInMemory()) {
 508  442
             return null;
 509  
         }
 510  267
         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  1245
         if (dfos == null) {
 521  2
             return;
 522  
         }
 523  1243
         File outputFile = dfos.getFile();
 524  
 
 525  1243
         if (outputFile != null && outputFile.exists()) {
 526  524
             outputFile.delete();
 527  
         }
 528  1243
     }
 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  2171
         if (tempFile == null) {
 543  2171
             File tempDir = repository;
 544  2171
             if (tempDir == null) {
 545  2164
                 tempDir = new File(System.getProperty("java.io.tmpdir"));
 546  
             }
 547  
 
 548  2171
             String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
 549  
 
 550  2171
             tempFile = new File(tempDir, tempFileName);
 551  
         }
 552  2171
         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  2171
         final int limit = 100000000;
 565  2171
         int current = COUNTER.getAndIncrement();
 566  2171
         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  2171
         if (current < limit) {
 571  2171
             id = ("00000000" + id).substring(id.length());
 572  
         }
 573  2171
         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  0
         return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
 584  0
                       getName(), getStoreLocation(), Long.valueOf(getSize()),
 585  0
                       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  32
         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  2159
         headers = pHeaders;
 602  2159
     }
 603  
 
 604  
 }