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