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 java.io.BufferedInputStream;
    20  import java.io.BufferedOutputStream;
    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.ObjectInputStream;
    28  import java.io.ObjectOutputStream;
    29  import java.io.OutputStream;
    30  import java.io.UnsupportedEncodingException;
    31  import java.util.Map;
    32
    33  import org.apache.commons.fileupload.FileItem;
    34  import org.apache.commons.fileupload.FileItemHeaders;
    35  import org.apache.commons.fileupload.FileItemHeadersSupport;
    36  import org.apache.commons.fileupload.FileUploadException;
    37  import org.apache.commons.fileupload.ParameterParser;
    38  import org.apache.commons.io.IOUtils;
    39  import org.apache.commons.io.output.DeferredFileOutputStream;
    40
    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   * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
    48   * {@link org.apache.commons.fileupload.DiskFileUpload
    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>When using the <code>DiskFileItemFactory</code>, then you should
    56   * consider the following: Temporary files are automatically deleted as
    57   * soon as they are no longer needed. (More precisely, when the
    58   * corresponding instance of {@link java.io.File} is garbage collected.)
    59   * This is done by the so-called reaper thread, which is started
    60   * automatically when the class {@link org.apache.commons.io.FileCleaner}
    61   * is loaded.
    62   * It might make sense to terminate that thread, for example, if
    63   * your web application ends. See the section on "Resource cleanup"
    64   * in the users guide of commons-fileupload.</p>
    65   *
    66   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
    67   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
    68   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    69   * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
    70   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
    71   * @author Sean C. Sullivan
    72   *
    73   * @since FileUpload 1.1
    74   *
    75   * @version $Id: DiskFileItem.java 607869 2008-01-01 16:42:17Z jochen $
    76   */
    77  public class DiskFileItem
    78      implements FileItem, FileItemHeadersSupport {
    79
    80      // ----------------------------------------------------- Manifest constants
    81
    82      /**
    83       * The UID to use when serializing this instance.
    84       */
    85      private static final long serialVersionUID = 2237570099615271025L;
    86
    87
    88      /**
    89       * Default content charset to be used when no explicit charset
    90       * parameter is provided by the sender. Media subtypes of the
    91       * "text" type are defined to have a default charset value of
    92       * "ISO-8859-1" when received via HTTP.
    93       */
    94      public static final String DEFAULT_CHARSET = "ISO-8859-1";
    95
    96
    97      // ----------------------------------------------------------- Data members
    98
    99
    100     /**
    101      * UID used in unique file name generation.
    102      */
    103     private static final String UID =
    104             new java.rmi.server.UID().toString()
    105                 .replace(':', '_').replace('-', '_');
    106
    107     /**
    108      * Counter used in unique identifier generation.
    109      */
    110     private static int counter = 0;
    111
    112
    113     /**
    114      * The name of the form field as provided by the browser.
    115      */
    116     private String fieldName;
    117
    118
    119     /**
    120      * The content type passed by the browser, or <code>null</code> if
    121      * not defined.
    122      */
    123     private String contentType;
    124
    125
    126     /**
    127      * Whether or not this item is a simple form field.
    128      */
    129     private boolean isFormField;
    130
    131
    132     /**
    133      * The original filename in the user's filesystem.
    134      */
    135     private String fileName;
    136
    137
    138     /**
    139      * The size of the item, in bytes. This is used to cache the size when a
    140      * file item is moved from its original location.
    141      */
    142     private long size = -1;
    143
    144
    145     /**
    146      * The threshold above which uploads will be stored on disk.
    147      */
    148     private int sizeThreshold;
    149
    150
    151     /**
    152      * The directory in which uploaded files will be stored, if stored on disk.
    153      */
    154     private File repository;
    155
    156
    157     /**
    158      * Cached contents of the file.
    159      */
    160     private byte[] cachedContent;
    161
    162
    163     /**
    164      * Output stream for this item.
    165      */
    166     private transient DeferredFileOutputStream dfos;
    167
    168     /**
    169      * The temporary file to use.
    170      */
    171     private transient File tempFile;
    172
    173     /**
    174      * File to allow for serialization of the content of this item.
    175      */
    176     private File dfosFile;
    177
    178     /**
    179      * The file items headers.
    180      */
    181     private FileItemHeaders headers;
    182
    183     // ----------------------------------------------------------- Constructors
    184
    185
    186     /**
    187      * Constructs a new <code>DiskFileItem</code> instance.
    188      *
    189      * @param fieldName     The name of the form field.
    190      * @param contentType   The content type passed by the browser or
    191      *                      <code>null</code> if not specified.
    192      * @param isFormField   Whether or not this item is a plain form field, as
    193      *                      opposed to a file upload.
    194      * @param fileName      The original filename in the user's filesystem, or
    195      *                      <code>null</code> if not specified.
    196      * @param sizeThreshold The threshold, in bytes, below which items will be
    197      *                      retained in memory and above which they will be
    198      *                      stored as a file.
    199      * @param repository    The data repository, which is the directory in
    200      *                      which files will be created, should the item size
    201      *                      exceed the threshold.
    202      */
    203     public DiskFileItem(String fieldName,
    204             String contentType, boolean isFormField, String fileName,
    205             int sizeThreshold, File repository) {
    206         this.fieldName = fieldName;
    207         this.contentType = contentType;
    208         this.isFormField = isFormField;
    209         this.fileName = fileName;
    210         this.sizeThreshold = sizeThreshold;
    211         this.repository = repository;
    212     }
    213
    214
    215     // ------------------------------- Methods from javax.activation.DataSource
    216
    217
    218     /**
    219      * Returns an {@link java.io.InputStream InputStream} that can be
    220      * used to retrieve the contents of the file.
    221      *
    222      * @return An {@link java.io.InputStream InputStream} that can be
    223      *         used to retrieve the contents of the file.
    224      *
    225      * @throws IOException if an error occurs.
    226      */
    227     public InputStream getInputStream()
    228         throws IOException {
    229         if (!isInMemory()) {
    230             return new FileInputStream(dfos.getFile());
    231         }
    232
    233         if (cachedContent == null) {
    234             cachedContent = dfos.getData();
    235         }
    236         return new ByteArrayInputStream(cachedContent);
    237     }
    238
    239
    240     /**
    241      * Returns the content type passed by the agent or <code>null</code> if
    242      * not defined.
    243      *
    244      * @return The content type passed by the agent or <code>null</code> if
    245      *         not defined.
    246      */
    247     public String getContentType() {
    248         return contentType;
    249     }
    250
    251
    252     /**
    253      * Returns the content charset passed by the agent or <code>null</code> if
    254      * not defined.
    255      *
    256      * @return The content charset passed by the agent or <code>null</code> if
    257      *         not defined.
    258      */
    259     public String getCharSet() {
    260         ParameterParser parser = new ParameterParser();
    261         parser.setLowerCaseNames(true);
    262         // Parameter parser can handle null input
    263         Map params = parser.parse(getContentType(), ';');
    264         return (String) params.get("charset");
    265     }
    266
    267
    268     /**
    269      * Returns the original filename in the client's filesystem.
    270      *
    271      * @return The original filename in the client's filesystem.
    272      */
    273     public String getName() {
    274         return fileName;
    275     }
    276
    277
    278     // ------------------------------------------------------- FileItem methods
    279
    280
    281     /**
    282      * Provides a hint as to whether or not the file contents will be read
    283      * from memory.
    284      *
    285      * @return <code>true</code> if the file contents will be read
    286      *         from memory; <code>false</code> otherwise.
    287      */
    288     public boolean isInMemory() {
    289         if (cachedContent != null) {
    290             return true;
    291         }
    292         return dfos.isInMemory();
    293     }
    294
    295
    296     /**
    297      * Returns the size of the file.
    298      *
    299      * @return The size of the file, in bytes.
    300      */
    301     public long getSize() {
    302         if (size >= 0) {
    303             return size;
    304         } else if (cachedContent != null) {
    305             return cachedContent.length;
    306         } else if (dfos.isInMemory()) {
    307             return dfos.getData().length;
    308         } else {
    309             return dfos.getFile().length();
    310         }
    311     }
    312
    313
    314     /**
    315      * Returns the contents of the file as an array of bytes.  If the
    316      * contents of the file were not yet cached in memory, they will be
    317      * loaded from the disk storage and cached.
    318      *
    319      * @return The contents of the file as an array of bytes.
    320      */
    321     public byte[] get() {
    322         if (isInMemory()) {
    323             if (cachedContent == null) {
    324                 cachedContent = dfos.getData();
    325             }
    326             return cachedContent;
    327         }
    328
    329         byte[] fileData = new byte[(int) getSize()];
    330         FileInputStream fis = null;
    331
    332         try {
    333             fis = new FileInputStream(dfos.getFile());
    334             fis.read(fileData);
    335         } catch (IOException e) {
    336             fileData = null;
    337         } finally {
    338             if (fis != null) {
    339                 try {
    340                     fis.close();
    341                 } catch (IOException e) {
    342                     // ignore
    343                 }
    344             }
    345         }
    346
    347         return fileData;
    348     }
    349
    350
    351     /**
    352      * Returns the contents of the file as a String, using the specified
    353      * encoding.  This method uses {@link #get()} to retrieve the
    354      * contents of the file.
    355      *
    356      * @param charset The charset to use.
    357      *
    358      * @return The contents of the file, as a string.
    359      *
    360      * @throws UnsupportedEncodingException if the requested character
    361      *                                      encoding is not available.
    362      */
    363     public String getString(final String charset)
    364         throws UnsupportedEncodingException {
    365         return new String(get(), charset);
    366     }
    367
    368
    369     /**
    370      * Returns the contents of the file as a String, using the default
    371      * character encoding.  This method uses {@link #get()} to retrieve the
    372      * contents of the file.
    373      *
    374      * @return The contents of the file, as a string.
    375      *
    376      * @todo Consider making this method throw UnsupportedEncodingException.
    377      */
    378     public String getString() {
    379         byte[] rawdata = get();
    380         String charset = getCharSet();
    381         if (charset == null) {
    382             charset = DEFAULT_CHARSET;
    383         }
    384         try {
    385             return new String(rawdata, charset);
    386         } catch (UnsupportedEncodingException e) {
    387             return new String(rawdata);
    388         }
    389     }
    390
    391
    392     /**
    393      * A convenience method to write an uploaded item to disk. The client code
    394      * is not concerned with whether or not the item is stored in memory, or on
    395      * disk in a temporary location. They just want to write the uploaded item
    396      * to a file.
    397      * <p>
    398      * This implementation first attempts to rename the uploaded item to the
    399      * specified destination file, if the item was originally written to disk.
    400      * Otherwise, the data will be copied to the specified file.
    401      * <p>
    402      * This method is only guaranteed to work <em>once</em>, the first time it
    403      * is invoked for a particular item. This is because, in the event that the
    404      * method renames a temporary file, that file will no longer be available
    405      * to copy or rename again at a later time.
    406      *
    407      * @param file The <code>File</code> into which the uploaded item should
    408      *             be stored.
    409      *
    410      * @throws Exception if an error occurs.
    411      */
    412     public void write(File file) throws Exception {
    413         if (isInMemory()) {
    414             FileOutputStream fout = null;
    415             try {
    416                 fout = new FileOutputStream(file);
    417                 fout.write(get());
    418             } finally {
    419                 if (fout != null) {
    420                     fout.close();
    421                 }
    422             }
    423         } else {
    424             File outputFile = getStoreLocation();
    425             if (outputFile != null) {
    426                 // Save the length of the file
    427                 size = outputFile.length();
    428                 /*
    429                  * The uploaded file is being stored on disk
    430                  * in a temporary location so move it to the
    431                  * desired file.
    432                  */
    433                 if (!outputFile.renameTo(file)) {
    434                     BufferedInputStream in = null;
    435                     BufferedOutputStream out = null;
    436                     try {
    437                         in = new BufferedInputStream(
    438                             new FileInputStream(outputFile));
    439                         out = new BufferedOutputStream(
    440                                 new FileOutputStream(file));
    441                         IOUtils.copy(in, out);
    442                     } finally {
    443                         if (in != null) {
    444                             try {
    445                                 in.close();
    446                             } catch (IOException e) {
    447                                 // ignore
    448                             }
    449                         }
    450                         if (out != null) {
    451                             try {
    452                                 out.close();
    453                             } catch (IOException e) {
    454                                 // ignore
    455                             }
    456                         }
    457                     }
    458                 }
    459             } else {
    460                 /*
    461                  * For whatever reason we cannot write the
    462                  * file to disk.
    463                  */
    464                 throw new FileUploadException(
    465                     "Cannot write uploaded file to disk!");
    466             }
    467         }
    468     }
    469
    470
    471     /**
    472      * Deletes the underlying storage for a file item, including deleting any
    473      * associated temporary disk file. Although this storage will be deleted
    474      * automatically when the <code>FileItem</code> instance is garbage
    475      * collected, this method can be used to ensure that this is done at an
    476      * earlier time, thus preserving system resources.
    477      */
    478     public void delete() {
    479         cachedContent = null;
    480         File outputFile = getStoreLocation();
    481         if (outputFile != null && outputFile.exists()) {
    482             outputFile.delete();
    483         }
    484     }
    485
    486
    487     /**
    488      * Returns the name of the field in the multipart form corresponding to
    489      * this file item.
    490      *
    491      * @return The name of the form field.
    492      *
    493      * @see #setFieldName(java.lang.String)
    494      *
    495      */
    496     public String getFieldName() {
    497         return fieldName;
    498     }
    499
    500
    501     /**
    502      * Sets the field name used to reference this file item.
    503      *
    504      * @param fieldName The name of the form field.
    505      *
    506      * @see #getFieldName()
    507      *
    508      */
    509     public void setFieldName(String fieldName) {
    510         this.fieldName = fieldName;
    511     }
    512
    513
    514     /**
    515      * Determines whether or not a <code>FileItem</code> instance represents
    516      * a simple form field.
    517      *
    518      * @return <code>true</code> if the instance represents a simple form
    519      *         field; <code>false</code> if it represents an uploaded file.
    520      *
    521      * @see #setFormField(boolean)
    522      *
    523      */
    524     public boolean isFormField() {
    525         return isFormField;
    526     }
    527
    528
    529     /**
    530      * Specifies whether or not a <code>FileItem</code> instance represents
    531      * a simple form field.
    532      *
    533      * @param state <code>true</code> if the instance represents a simple form
    534      *              field; <code>false</code> if it represents an uploaded file.
    535      *
    536      * @see #isFormField()
    537      *
    538      */
    539     public void setFormField(boolean state) {
    540         isFormField = state;
    541     }
    542
    543
    544     /**
    545      * Returns an {@link java.io.OutputStream OutputStream} that can
    546      * be used for storing the contents of the file.
    547      *
    548      * @return An {@link java.io.OutputStream OutputStream} that can be used
    549      *         for storing the contensts of the file.
    550      *
    551      * @throws IOException if an error occurs.
    552      */
    553     public OutputStream getOutputStream()
    554         throws IOException {
    555         if (dfos == null) {
    556             File outputFile = getTempFile();
    557             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
    558         }
    559         return dfos;
    560     }
    561
    562
    563     // --------------------------------------------------------- Public methods
    564
    565
    566     /**
    567      * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
    568      * data's temporary location on the disk. Note that for
    569      * <code>FileItem</code>s that have their data stored in memory,
    570      * this method will return <code>null</code>. When handling large
    571      * files, you can use {@link java.io.File#renameTo(java.io.File)} to
    572      * move the file to new location without copying the data, if the
    573      * source and destination locations reside within the same logical
    574      * volume.
    575      *
    576      * @return The data file, or <code>null</code> if the data is stored in
    577      *         memory.
    578      */
    579     public File getStoreLocation() {
    580         return dfos == null ? null : dfos.getFile();
    581     }
    582
    583
    584     // ------------------------------------------------------ Protected methods
    585
    586
    587     /**
    588      * Removes the file contents from the temporary storage.
    589      */
    590     protected void finalize() {
    591         File outputFile = dfos.getFile();
    592
    593         if (outputFile != null && outputFile.exists()) {
    594             outputFile.delete();
    595         }
    596     }
    597
    598
    599     /**
    600      * Creates and returns a {@link java.io.File File} representing a uniquely
    601      * named temporary file in the configured repository path. The lifetime of
    602      * the file is tied to the lifetime of the <code>FileItem</code> instance;
    603      * the file will be deleted when the instance is garbage collected.
    604      *
    605      * @return The {@link java.io.File File} to be used for temporary storage.
    606      */
    607     protected File getTempFile() {
    608         if (tempFile == null) {
    609             File tempDir = repository;
    610             if (tempDir == null) {
    611                 tempDir = new File(System.getProperty("java.io.tmpdir"));
    612             }
    613
    614             String tempFileName =
    615                 "upload_" + UID + "_" + getUniqueId() + ".tmp";
    616
    617             tempFile = new File(tempDir, tempFileName);
    618         }
    619         return tempFile;
    620     }
    621
    622
    623     // -------------------------------------------------------- Private methods
    624
    625
    626     /**
    627      * Returns an identifier that is unique within the class loader used to
    628      * load this class, but does not have random-like apearance.
    629      *
    630      * @return A String with the non-random looking instance identifier.
    631      */
    632     private static String getUniqueId() {
    633         final int limit = 100000000;
    634         int current;
    635         synchronized (DiskFileItem.class) {
    636             current = counter++;
    637         }
    638         String id = Integer.toString(current);
    639
    640         // If you manage to get more than 100 million of ids, you'll
    641         // start getting ids longer than 8 characters.
    642         if (current < limit) {
    643             id = ("00000000" + id).substring(id.length());
    644         }
    645         return id;
    646     }
    647
    648
    649
    650
    651     /**
    652      * Returns a string representation of this object.
    653      *
    654      * @return a string representation of this object.
    655      */
    656     public String toString() {
    657         return "name=" + this.getName()
    658             + ", StoreLocation="
    659             + String.valueOf(this.getStoreLocation())
    660             + ", size="
    661             + this.getSize()
    662             + "bytes, "
    663             + "isFormField=" + isFormField()
    664             + ", FieldName="
    665             + this.getFieldName();
    666     }
    667
    668
    669     // -------------------------------------------------- Serialization methods
    670
    671
    672     /**
    673      * Writes the state of this object during serialization.
    674      *
    675      * @param out The stream to which the state should be written.
    676      *
    677      * @throws IOException if an error occurs.
    678      */
    679     private void writeObject(ObjectOutputStream out) throws IOException {
    680         // Read the data
    681         if (dfos.isInMemory()) {
    682             cachedContent = get();
    683         } else {
    684             cachedContent = null;
    685             dfosFile = dfos.getFile();
    686         }
    687
    688         // write out values
    689         out.defaultWriteObject();
    690     }
    691
    692     /**
    693      * Reads the state of this object during deserialization.
    694      *
    695      * @param in The stream from which the state should be read.
    696      *
    697      * @throws IOException if an error occurs.
    698      * @throws ClassNotFoundException if class cannot be found.
    699      */
    700     private void readObject(ObjectInputStream in)
    701             throws IOException, ClassNotFoundException {
    702         // read values
    703         in.defaultReadObject();
    704
    705         OutputStream output = getOutputStream();
    706         if (cachedContent != null) {
    707             output.write(cachedContent);
    708         } else {
    709             FileInputStream input = new FileInputStream(dfosFile);
    710             IOUtils.copy(input, output);
    711             dfosFile.delete();
    712             dfosFile = null;
    713         }
    714         output.close();
    715
    716         cachedContent = null;
    717     }
    718
    719     /**
    720      * Returns the file item headers.
    721      * @return The file items headers.
    722      */
    723     public FileItemHeaders getHeaders() {
    724         return headers;
    725     }
    726
    727     /**
    728      * Sets the file item headers.
    729      * @param pHeaders The file items headers.
    730      */
    731     public void setHeaders(FileItemHeaders pHeaders) {
    732         headers = pHeaders;
    733     }
    734 }