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