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