Coverage Report - org.apache.commons.fileupload.disk.DiskFileItem
 
Classes in this File Line Coverage Branch Coverage Complexity
DiskFileItem
69%
81/116
60%
30/50
2.462
 
 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  2174
     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  2174
     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  2174
             int sizeThreshold, File repository) {
 183  2174
         this.fieldName = fieldName;
 184  2174
         this.contentType = contentType;
 185  2174
         this.isFormField = isFormField;
 186  2174
         this.fileName = fileName;
 187  2174
         this.sizeThreshold = sizeThreshold;
 188  2174
         this.repository = repository;
 189  2174
     }
 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  0
         if (!isInMemory()) {
 206  0
             return new FileInputStream(dfos.getFile());
 207  
         }
 208  
 
 209  0
         if (cachedContent == null) {
 210  0
             cachedContent = dfos.getData();
 211  
         }
 212  0
         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  43
         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  35
         ParameterParser parser = new ParameterParser();
 236  35
         parser.setLowerCaseNames(true);
 237  
         // Parameter parser can handle null input
 238  35
         Map<String, String> params = parser.parse(getContentType(), ';');
 239  35
         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  19
         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  2432
         if (cachedContent != null) {
 268  2
             return true;
 269  
         }
 270  2430
         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  536
         if (size >= 0) {
 281  0
             return size;
 282  536
         } else if (cachedContent != null) {
 283  0
             return cachedContent.length;
 284  536
         } else if (dfos.isInMemory()) {
 285  4
             return dfos.getData().length;
 286  
         } else {
 287  532
             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  1451
         if (isInMemory()) {
 302  922
             if (cachedContent == null && dfos != null) {
 303  920
                 cachedContent = dfos.getData();
 304  
             }
 305  922
             return cachedContent;
 306  
         }
 307  
 
 308  529
         byte[] fileData = new byte[(int) getSize()];
 309  529
         InputStream fis = null;
 310  
 
 311  
         try {
 312  529
             fis = new FileInputStream(dfos.getFile());
 313  529
             IOUtils.readFully(fis, fileData);
 314  0
         } catch (IOException e) {
 315  0
             fileData = null;
 316  
         } finally {
 317  529
             IOUtils.closeQuietly(fis);
 318  529
         }
 319  
 
 320  529
         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  0
         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  35
         byte[] rawdata = get();
 353  35
         String charset = getCharSet();
 354  35
         if (charset == null) {
 355  35
             charset = defaultCharset;
 356  
         }
 357  
         try {
 358  35
             return new String(rawdata, charset);
 359  0
         } catch (UnsupportedEncodingException e) {
 360  0
             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  0
         if (isInMemory()) {
 387  0
             FileOutputStream fout = null;
 388  
             try {
 389  0
                 fout = new FileOutputStream(file);
 390  0
                 fout.write(get());
 391  0
                 fout.close();
 392  
             } finally {
 393  0
                 IOUtils.closeQuietly(fout);
 394  0
             }
 395  0
         } else {
 396  0
             File outputFile = getStoreLocation();
 397  0
             if (outputFile != null) {
 398  
                 // Save the length of the file
 399  0
                 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  0
                 FileUtils.moveFile(outputFile, file);
 406  
             } else {
 407  
                 /*
 408  
                  * For whatever reason we cannot write the
 409  
                  * file to disk.
 410  
                  */
 411  0
                 throw new FileUploadException(
 412  
                     "Cannot write uploaded file to disk!");
 413  
             }
 414  
         }
 415  0
     }
 416  
 
 417  
     /**
 418  
      * Deletes the underlying storage for a file item, including deleting any
 419  
      * associated temporary disk file. Although this storage will be deleted
 420  
      * automatically when the <code>FileItem</code> instance is garbage
 421  
      * collected, this method can be used to ensure that this is done at an
 422  
      * earlier time, thus preserving system resources.
 423  
      */
 424  
     @Override
 425  
     public void delete() {
 426  707
         cachedContent = null;
 427  707
         File outputFile = getStoreLocation();
 428  707
         if (outputFile != null && !isInMemory() && outputFile.exists()) {
 429  265
             outputFile.delete();
 430  
         }
 431  707
     }
 432  
 
 433  
     /**
 434  
      * Returns the name of the field in the multipart form corresponding to
 435  
      * this file item.
 436  
      *
 437  
      * @return The name of the form field.
 438  
      *
 439  
      * @see #setFieldName(java.lang.String)
 440  
      *
 441  
      */
 442  
     @Override
 443  
     public String getFieldName() {
 444  1445
         return fieldName;
 445  
     }
 446  
 
 447  
     /**
 448  
      * Sets the field name used to reference this file item.
 449  
      *
 450  
      * @param fieldName The name of the form field.
 451  
      *
 452  
      * @see #getFieldName()
 453  
      *
 454  
      */
 455  
     @Override
 456  
     public void setFieldName(String fieldName) {
 457  0
         this.fieldName = fieldName;
 458  0
     }
 459  
 
 460  
     /**
 461  
      * Determines whether or not a <code>FileItem</code> instance represents
 462  
      * a simple form field.
 463  
      *
 464  
      * @return <code>true</code> if the instance represents a simple form
 465  
      *         field; <code>false</code> if it represents an uploaded file.
 466  
      *
 467  
      * @see #setFormField(boolean)
 468  
      *
 469  
      */
 470  
     @Override
 471  
     public boolean isFormField() {
 472  32
         return isFormField;
 473  
     }
 474  
 
 475  
     /**
 476  
      * Specifies whether or not a <code>FileItem</code> instance represents
 477  
      * a simple form field.
 478  
      *
 479  
      * @param state <code>true</code> if the instance represents a simple form
 480  
      *              field; <code>false</code> if it represents an uploaded file.
 481  
      *
 482  
      * @see #isFormField()
 483  
      *
 484  
      */
 485  
     @Override
 486  
     public void setFormField(boolean state) {
 487  0
         isFormField = state;
 488  0
     }
 489  
 
 490  
     /**
 491  
      * Returns an {@link java.io.OutputStream OutputStream} that can
 492  
      * be used for storing the contents of the file.
 493  
      *
 494  
      * @return An {@link java.io.OutputStream OutputStream} that can be used
 495  
      *         for storing the contents of the file.
 496  
      *
 497  
      * @throws IOException if an error occurs.
 498  
      */
 499  
     @Override
 500  
     public OutputStream getOutputStream()
 501  
         throws IOException {
 502  2172
         if (dfos == null) {
 503  2172
             File outputFile = getTempFile();
 504  2172
             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
 505  
         }
 506  2172
         return dfos;
 507  
     }
 508  
 
 509  
     // --------------------------------------------------------- Public methods
 510  
 
 511  
     /**
 512  
      * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
 513  
      * data's temporary location on the disk. Note that for
 514  
      * <code>FileItem</code>s that have their data stored in memory,
 515  
      * this method will return <code>null</code>. When handling large
 516  
      * files, you can use {@link java.io.File#renameTo(java.io.File)} to
 517  
      * move the file to new location without copying the data, if the
 518  
      * source and destination locations reside within the same logical
 519  
      * volume.
 520  
      *
 521  
      * @return The data file, or <code>null</code> if the data is stored in
 522  
      *         memory.
 523  
      */
 524  
     public File getStoreLocation() {
 525  709
         if (dfos == null) {
 526  0
             return null;
 527  
         }
 528  709
         if (isInMemory()) {
 529  442
             return null;
 530  
         }
 531  267
         return dfos.getFile();
 532  
     }
 533  
 
 534  
     // ------------------------------------------------------ Protected methods
 535  
 
 536  
     /**
 537  
      * Removes the file contents from the temporary storage.
 538  
      */
 539  
     @Override
 540  
     protected void finalize() {
 541  1417
         if (dfos == null || dfos.isInMemory()) {
 542  892
             return;
 543  
         }
 544  525
         File outputFile = dfos.getFile();
 545  
 
 546  525
         if (outputFile != null && outputFile.exists()) {
 547  262
             outputFile.delete();
 548  
         }
 549  525
     }
 550  
 
 551  
     /**
 552  
      * Creates and returns a {@link java.io.File File} representing a uniquely
 553  
      * named temporary file in the configured repository path. The lifetime of
 554  
      * the file is tied to the lifetime of the <code>FileItem</code> instance;
 555  
      * the file will be deleted when the instance is garbage collected.
 556  
      * <p>
 557  
      * <b>Note: Subclasses that override this method must ensure that they return the
 558  
      * same File each time.</b>
 559  
      *
 560  
      * @return The {@link java.io.File File} to be used for temporary storage.
 561  
      */
 562  
     protected File getTempFile() {
 563  2172
         if (tempFile == null) {
 564  2172
             File tempDir = repository;
 565  2172
             if (tempDir == null) {
 566  2165
                 tempDir = new File(System.getProperty("java.io.tmpdir"));
 567  
             }
 568  
 
 569  2172
             String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
 570  
 
 571  2172
             tempFile = new File(tempDir, tempFileName);
 572  
         }
 573  2172
         return tempFile;
 574  
     }
 575  
 
 576  
     // -------------------------------------------------------- Private methods
 577  
 
 578  
     /**
 579  
      * Returns an identifier that is unique within the class loader used to
 580  
      * load this class, but does not have random-like appearance.
 581  
      *
 582  
      * @return A String with the non-random looking instance identifier.
 583  
      */
 584  
     private static String getUniqueId() {
 585  2172
         final int limit = 100000000;
 586  2172
         int current = COUNTER.getAndIncrement();
 587  2172
         String id = Integer.toString(current);
 588  
 
 589  
         // If you manage to get more than 100 million of ids, you'll
 590  
         // start getting ids longer than 8 characters.
 591  2172
         if (current < limit) {
 592  2172
             id = ("00000000" + id).substring(id.length());
 593  
         }
 594  2172
         return id;
 595  
     }
 596  
 
 597  
     /**
 598  
      * Returns a string representation of this object.
 599  
      *
 600  
      * @return a string representation of this object.
 601  
      */
 602  
     @Override
 603  
     public String toString() {
 604  0
         return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
 605  0
                       getName(), getStoreLocation(), Long.valueOf(getSize()),
 606  0
                       Boolean.valueOf(isFormField()), getFieldName());
 607  
     }
 608  
 
 609  
     /**
 610  
      * Returns the file item headers.
 611  
      * @return The file items headers.
 612  
      */
 613  
     @Override
 614  
     public FileItemHeaders getHeaders() {
 615  32
         return headers;
 616  
     }
 617  
 
 618  
     /**
 619  
      * Sets the file item headers.
 620  
      * @param pHeaders The file items headers.
 621  
      */
 622  
     @Override
 623  
     public void setHeaders(FileItemHeaders pHeaders) {
 624  2160
         headers = pHeaders;
 625  2160
     }
 626  
 
 627  
     /**
 628  
      * Returns the default charset for use when no explicit charset
 629  
      * parameter is provided by the sender.
 630  
      * @return the default charset
 631  
      */
 632  
     public String getDefaultCharset() {
 633  0
         return defaultCharset;
 634  
     }
 635  
 
 636  
     /**
 637  
      * Sets the default charset for use when no explicit charset
 638  
      * parameter is provided by the sender.
 639  
      * @param charset the default charset
 640  
      */
 641  
     public void setDefaultCharset(String charset) {
 642  2169
         defaultCharset = charset;
 643  2169
     }
 644  
 }