View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.fileupload.disk;
    18
    19  import static java.lang.String.format;
    20
    21  import java.io.ByteArrayInputStream;
    22  import java.io.File;
    23  import java.io.FileInputStream;
    24  import java.io.FileOutputStream;
    25  import java.io.IOException;
    26  import java.io.InputStream;
    27  import java.io.OutputStream;
    28  import java.io.UnsupportedEncodingException;
    29  import java.util.Map;
    30  import java.util.UUID;
    31  import java.util.concurrent.atomic.AtomicInteger;
    32
    33  import org.apache.commons.fileupload.FileItem;
    34  import org.apache.commons.fileupload.FileItemHeaders;
    35  import org.apache.commons.fileupload.FileUploadException;
    36  import org.apache.commons.fileupload.ParameterParser;
    37  import org.apache.commons.fileupload.util.Streams;
    38  import org.apache.commons.io.FileUtils;
    39  import org.apache.commons.io.IOUtils;
    40  import org.apache.commons.io.output.DeferredFileOutputStream;
    41
    42  /**
    43   * <p> The default implementation of the
    44   * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
    45   *
    46   * <p> After retrieving an instance of this class from a {@link
    47   * DiskFileItemFactory} instance (see
    48   * {@link org.apache.commons.fileupload.servlet.ServletFileUpload
    49   * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
    50   * either request all contents of file at once using {@link #get()} or
    51   * request an {@link java.io.InputStream InputStream} with
    52   * {@link #getInputStream()} and process the file without attempting to load
    53   * it into memory, which may come handy with large files.
    54   *
    55   * <p>Temporary files, which are created for file items, should be
    56   * deleted later on. The best way to do this is using a
    57   * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the
    58   * {@link DiskFileItemFactory}. However, if you do use such a tracker,
    59   * then you must consider the following: Temporary files are automatically
    60   * deleted as soon as they are no longer needed. (More precisely, when the
    61   * corresponding instance of {@link java.io.File} is garbage collected.)
    62   * This is done by the so-called reaper thread, which is started and stopped
    63   * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when
    64   * there are files to be tracked.
    65   * It might make sense to terminate that thread, for example, if
    66   * your web application ends. See the section on "Resource cleanup"
    67   * in the users guide of commons-fileupload.</p>
    68   *
    69   * @since FileUpload 1.1
    70   */
    71  public class DiskFileItem
    72      implements FileItem {
    73
    74      // ----------------------------------------------------- Manifest constants
    75
    76      /**
    77       * Default content charset to be used when no explicit charset
    78       * parameter is provided by the sender. Media subtypes of the
    79       * "text" type are defined to have a default charset value of
    80       * "ISO-8859-1" when received via HTTP.
    81       */
    82      public static final String DEFAULT_CHARSET = "ISO-8859-1";
    83
    84      // ----------------------------------------------------------- Data members
    85
    86      /**
    87       * UID used in unique file name generation.
    88       */
    89      private static final String UID =
    90              UUID.randomUUID().toString().replace('-', '_');
    91
    92      /**
    93       * Counter used in unique identifier generation.
    94       */
    95      private static final AtomicInteger COUNTER = new AtomicInteger(0);
    96
    97      /**
    98       * The name of the form field as provided by the browser.
    99       */
    100     private String fieldName;
    101
    102     /**
    103      * The content type passed by the browser, or <code>null</code> if
    104      * not defined.
    105      */
    106     private final String contentType;
    107
    108     /**
    109      * Whether or not this item is a simple form field.
    110      */
    111     private boolean isFormField;
    112
    113     /**
    114      * The original filename in the user's filesystem.
    115      */
    116     private final String fileName;
    117
    118     /**
    119      * The size of the item, in bytes. This is used to cache the size when a
    120      * file item is moved from its original location.
    121      */
    122     private long size = -1;
    123
    124
    125     /**
    126      * The threshold above which uploads will be stored on disk.
    127      */
    128     private final int sizeThreshold;
    129
    130     /**
    131      * The directory in which uploaded files will be stored, if stored on disk.
    132      */
    133     private final File repository;
    134
    135     /**
    136      * Cached contents of the file.
    137      */
    138     private byte[] cachedContent;
    139
    140     /**
    141      * Output stream for this item.
    142      */
    143     private transient DeferredFileOutputStream dfos;
    144
    145     /**
    146      * The temporary file to use.
    147      */
    148     private transient File tempFile;
    149
    150     /**
    151      * The file items headers.
    152      */
    153     private FileItemHeaders headers;
    154
    155     /**
    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                 FileUtils.moveFile(outputFile, file);
    406             } else {
    407                 /*
    408                  * For whatever reason we cannot write the
    409                  * file to disk.
    410                  */
    411                 throw new FileUploadException(
    412                     "Cannot write uploaded file to disk!");
    413             }
    414         }
    415     }
    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         cachedContent = null;
    427         File outputFile = getStoreLocation();
    428         if (outputFile != null && !isInMemory() && outputFile.exists()) {
    429             outputFile.delete();
    430         }
    431     }
    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         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         this.fieldName = fieldName;
    458     }
    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         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         isFormField = state;
    488     }
    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         if (dfos == null) {
    503             File outputFile = getTempFile();
    504             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
    505         }
    506         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         if (dfos == null) {
    526             return null;
    527         }
    528         if (isInMemory()) {
    529             return null;
    530         }
    531         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         if (dfos == null || dfos.isInMemory()) {
    542             return;
    543         }
    544         File outputFile = dfos.getFile();
    545
    546         if (outputFile != null && outputFile.exists()) {
    547             outputFile.delete();
    548         }
    549     }
    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         if (tempFile == null) {
    564             File tempDir = repository;
    565             if (tempDir == null) {
    566                 tempDir = new File(System.getProperty("java.io.tmpdir"));
    567             }
    568
    569             String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
    570
    571             tempFile = new File(tempDir, tempFileName);
    572         }
    573         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         final int limit = 100000000;
    586         int current = COUNTER.getAndIncrement();
    587         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         if (current < limit) {
    592             id = ("00000000" + id).substring(id.length());
    593         }
    594         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         return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
    605                       getName(), getStoreLocation(), Long.valueOf(getSize()),
    606                       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         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         headers = pHeaders;
    625     }
    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         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         defaultCharset = charset;
    643     }
    644 }