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    }