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