    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.fileupload.disk;
    018
    019    import java.io.BufferedInputStream;
    020    import java.io.BufferedOutputStream;
    021    import java.io.ByteArrayInputStream;
    022    import java.io.File;
    023    import java.io.FileInputStream;
    024    import java.io.FileOutputStream;
    025    import java.io.IOException;
    026    import java.io.InputStream;
    027    import java.io.ObjectInputStream;
    028    import java.io.ObjectOutputStream;
    029    import java.io.OutputStream;
    030    import java.io.UnsupportedEncodingException;
    031    import java.util.Map;
    032
    033    import org.apache.commons.fileupload.FileItem;
    034    import org.apache.commons.fileupload.FileItemHeaders;
    035    import org.apache.commons.fileupload.FileItemHeadersSupport;
    036    import org.apache.commons.fileupload.FileUploadException;
    037    import org.apache.commons.fileupload.InvalidFileNameException;
    038    import org.apache.commons.fileupload.ParameterParser;
    039    import org.apache.commons.fileupload.util.Streams;
    040    import org.apache.commons.io.FileCleaningTracker;
    041    import org.apache.commons.io.IOUtils;
    042    import org.apache.commons.io.output.DeferredFileOutputStream;
    043
    044
    045    /**
    046     * <p> The default implementation of the
    047     * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
    048     *
    049     * <p> After retrieving an instance of this class from a {@link
    050     * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
    051     * {@link org.apache.commons.fileupload.DiskFileUpload
    052     * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
    053     * either request all contents of file at once using {@link #get()} or
    054     * request an {@link java.io.InputStream InputStream} with
    055     * {@link #getInputStream()} and process the file without attempting to load
    056     * it into memory, which may come handy with large files.
    057     *
    058     * <p>Temporary files, which are created for file items, should be
    059     * deleted later on. The best way to do this is using a
    060     * {@link FileCleaningTracker}, which you can set on the
    061     * {@link DiskFileItemFactory}. However, if you do use such a tracker,
    062     * then you must consider the following: Temporary files are automatically
    063     * deleted as soon as they are no longer needed. (More precisely, when the
    064     * corresponding instance of {@link java.io.File} is garbage collected.)
    065     * This is done by the so-called reaper thread, which is started
    066     * automatically when the class {@link org.apache.commons.io.FileCleaner}
    067     * is loaded.
    068     * It might make sense to terminate that thread, for example, if
    069     * your web application ends. See the section on "Resource cleanup"
    070     * in the users guide of commons-fileupload.</p>
    071     *
    072     * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
    073     * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
    074     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    075     * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
    076     * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
    077     * @author Sean C. Sullivan
    078     *
    079     * @since FileUpload 1.1
    080     *
    081     * @version $Id: DiskFileItem.java 963609 2010-07-13 06:56:47Z jochen $
    082     */
    083    public class DiskFileItem
    084        implements FileItem, FileItemHeadersSupport {
    085
    086        // ----------------------------------------------------- Manifest constants
    087
    088        /**
    089         * The UID to use when serializing this instance.
    090         */
    091        private static final long serialVersionUID = 2237570099615271025L;
    092
    093
    094        /**
    095         * Default content charset to be used when no explicit charset
    096         * parameter is provided by the sender. Media subtypes of the
    097         * "text" type are defined to have a default charset value of
    098         * "ISO-8859-1" when received via HTTP.
    099         */
    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    }