View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.fileupload.disk;
18  
19  import static java.lang.String.format;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedOutputStream;
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.ObjectInputStream;
30  import java.io.ObjectOutputStream;
31  import java.io.OutputStream;
32  import java.io.UnsupportedEncodingException;
33  import java.util.Map;
34  import java.util.UUID;
35  import java.util.concurrent.atomic.AtomicInteger;
36  
37  import org.apache.commons.fileupload.FileItem;
38  import org.apache.commons.fileupload.FileItemHeaders;
39  import org.apache.commons.fileupload.FileUploadException;
40  import org.apache.commons.fileupload.ParameterParser;
41  import org.apache.commons.fileupload.util.Streams;
42  import org.apache.commons.io.IOUtils;
43  import org.apache.commons.io.output.DeferredFileOutputStream;
44  
45  /**
46   * <p> The default implementation of the
47   * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
48   *
49   * <p> After retrieving an instance of this class from a {@link
50   * DiskFileItemFactory} instance (see
51   * {@link org.apache.commons.fileupload.servlet.ServletFileUpload
52   * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
53   * either request all contents of file at once using {@link #get()} or
54   * request an {@link java.io.InputStream InputStream} with
55   * {@link #getInputStream()} and process the file without attempting to load
56   * it into memory, which may come handy with large files.
57   *
58   * <p>Temporary files, which are created for file items, should be
59   * deleted later on. The best way to do this is using a
60   * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the
61   * {@link DiskFileItemFactory}. However, if you do use such a tracker,
62   * then you must consider the following: Temporary files are automatically
63   * deleted as soon as they are no longer needed. (More precisely, when the
64   * corresponding instance of {@link java.io.File} is garbage collected.)
65   * This is done by the so-called reaper thread, which is started and stopped
66   * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when
67   * there are files to be tracked.
68   * It might make sense to terminate that thread, for example, if
69   * your web application ends. See the section on "Resource cleanup"
70   * in the users guide of commons-fileupload.</p>
71   *
72   * @since FileUpload 1.1
73   *
74   * @version $Id: DiskFileItem.java 1569132 2014-02-17 22:41:56Z sebb $
75   */
76  public class DiskFileItem
77      implements FileItem {
78  
79      // ----------------------------------------------------- Manifest constants
80  
81      /**
82       * The UID to use when serializing this instance.
83       */
84      private static final long serialVersionUID = 2237570099615271025L;
85  
86      /**
87       * Default content charset to be used when no explicit charset
88       * parameter is provided by the sender. Media subtypes of the
89       * "text" type are defined to have a default charset value of
90       * "ISO-8859-1" when received via HTTP.
91       */
92      public static final String DEFAULT_CHARSET = "ISO-8859-1";
93  
94      // ----------------------------------------------------------- Data members
95  
96      /**
97       * UID used in unique file name generation.
98       */
99      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      * or {@code null} if the data cannot be read
302      */
303     public byte[] get() {
304         if (isInMemory()) {
305             if (cachedContent == null && dfos != null) {
306                 cachedContent = dfos.getData();
307             }
308             return cachedContent;
309         }
310 
311         byte[] fileData = new byte[(int) getSize()];
312         InputStream fis = null;
313 
314         try {
315             fis = new BufferedInputStream(new FileInputStream(dfos.getFile()));
316             IOUtils.readFully(fis, fileData);
317         } catch (IOException e) {
318             fileData = null;
319         } finally {
320             IOUtils.closeQuietly(fis);
321         }
322 
323         return fileData;
324     }
325 
326     /**
327      * Returns the contents of the file as a String, using the specified
328      * encoding.  This method uses {@link #get()} to retrieve the
329      * contents of the file.
330      *
331      * @param charset The charset to use.
332      *
333      * @return The contents of the file, as a string.
334      *
335      * @throws UnsupportedEncodingException if the requested character
336      *                                      encoding is not available.
337      */
338     public String getString(final String charset)
339         throws UnsupportedEncodingException {
340         return new String(get(), charset);
341     }
342 
343     /**
344      * Returns the contents of the file as a String, using the default
345      * character encoding.  This method uses {@link #get()} to retrieve the
346      * contents of the file.
347      *
348      * <b>TODO</b> Consider making this method throw UnsupportedEncodingException.
349      *
350      * @return The contents of the file, as a string.
351      */
352     public String getString() {
353         byte[] rawdata = get();
354         String charset = getCharSet();
355         if (charset == null) {
356             charset = DEFAULT_CHARSET;
357         }
358         try {
359             return new String(rawdata, charset);
360         } catch (UnsupportedEncodingException e) {
361             return new String(rawdata);
362         }
363     }
364 
365     /**
366      * A convenience method to write an uploaded item to disk. The client code
367      * is not concerned with whether or not the item is stored in memory, or on
368      * disk in a temporary location. They just want to write the uploaded item
369      * to a file.
370      * <p>
371      * This implementation first attempts to rename the uploaded item to the
372      * specified destination file, if the item was originally written to disk.
373      * Otherwise, the data will be copied to the specified file.
374      * <p>
375      * This method is only guaranteed to work <em>once</em>, the first time it
376      * is invoked for a particular item. This is because, in the event that the
377      * method renames a temporary file, that file will no longer be available
378      * to copy or rename again at a later time.
379      *
380      * @param file The <code>File</code> into which the uploaded item should
381      *             be stored.
382      *
383      * @throws Exception if an error occurs.
384      */
385     public void write(File file) throws Exception {
386         if (isInMemory()) {
387             FileOutputStream fout = null;
388             try {
389                 fout = new FileOutputStream(file);
390                 fout.write(get());
391             } finally {
392                 if (fout != null) {
393                     fout.close();
394                 }
395             }
396         } else {
397             File outputFile = getStoreLocation();
398             if (outputFile != null) {
399                 // Save the length of the file
400                 size = outputFile.length();
401                 /*
402                  * The uploaded file is being stored on disk
403                  * in a temporary location so move it to the
404                  * desired file.
405                  */
406                 if (!outputFile.renameTo(file)) {
407                     BufferedInputStream in = null;
408                     BufferedOutputStream out = null;
409                     try {
410                         in = new BufferedInputStream(
411                             new FileInputStream(outputFile));
412                         out = new BufferedOutputStream(
413                                 new FileOutputStream(file));
414                         IOUtils.copy(in, out);
415                     } finally {
416                         IOUtils.closeQuietly(in);
417                         IOUtils.closeQuietly(out);
418                     }
419                 }
420             } else {
421                 /*
422                  * For whatever reason we cannot write the
423                  * file to disk.
424                  */
425                 throw new FileUploadException(
426                     "Cannot write uploaded file to disk!");
427             }
428         }
429     }
430 
431     /**
432      * Deletes the underlying storage for a file item, including deleting any
433      * associated temporary disk file. Although this storage will be deleted
434      * automatically when the <code>FileItem</code> instance is garbage
435      * collected, this method can be used to ensure that this is done at an
436      * earlier time, thus preserving system resources.
437      */
438     public void delete() {
439         cachedContent = null;
440         File outputFile = getStoreLocation();
441         if (outputFile != null && outputFile.exists()) {
442             outputFile.delete();
443         }
444     }
445 
446     /**
447      * Returns the name of the field in the multipart form corresponding to
448      * this file item.
449      *
450      * @return The name of the form field.
451      *
452      * @see #setFieldName(java.lang.String)
453      *
454      */
455     public String getFieldName() {
456         return fieldName;
457     }
458 
459     /**
460      * Sets the field name used to reference this file item.
461      *
462      * @param fieldName The name of the form field.
463      *
464      * @see #getFieldName()
465      *
466      */
467     public void setFieldName(String fieldName) {
468         this.fieldName = fieldName;
469     }
470 
471     /**
472      * Determines whether or not a <code>FileItem</code> instance represents
473      * a simple form field.
474      *
475      * @return <code>true</code> if the instance represents a simple form
476      *         field; <code>false</code> if it represents an uploaded file.
477      *
478      * @see #setFormField(boolean)
479      *
480      */
481     public boolean isFormField() {
482         return isFormField;
483     }
484 
485     /**
486      * Specifies whether or not a <code>FileItem</code> instance represents
487      * a simple form field.
488      *
489      * @param state <code>true</code> if the instance represents a simple form
490      *              field; <code>false</code> if it represents an uploaded file.
491      *
492      * @see #isFormField()
493      *
494      */
495     public void setFormField(boolean state) {
496         isFormField = state;
497     }
498 
499     /**
500      * Returns an {@link java.io.OutputStream OutputStream} that can
501      * be used for storing the contents of the file.
502      *
503      * @return An {@link java.io.OutputStream OutputStream} that can be used
504      *         for storing the contensts of the file.
505      *
506      * @throws IOException if an error occurs.
507      */
508     public OutputStream getOutputStream()
509         throws IOException {
510         if (dfos == null) {
511             File outputFile = getTempFile();
512             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
513         }
514         return dfos;
515     }
516 
517     // --------------------------------------------------------- Public methods
518 
519     /**
520      * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
521      * data's temporary location on the disk. Note that for
522      * <code>FileItem</code>s that have their data stored in memory,
523      * this method will return <code>null</code>. When handling large
524      * files, you can use {@link java.io.File#renameTo(java.io.File)} to
525      * move the file to new location without copying the data, if the
526      * source and destination locations reside within the same logical
527      * volume.
528      *
529      * @return The data file, or <code>null</code> if the data is stored in
530      *         memory.
531      */
532     public File getStoreLocation() {
533         if (dfos == null) {
534             return null;
535         }
536         return dfos.getFile();
537     }
538 
539     // ------------------------------------------------------ Protected methods
540 
541     /**
542      * Removes the file contents from the temporary storage.
543      */
544     @Override
545     protected void finalize() {
546         if (dfos == null) {
547             return;
548         }
549         File outputFile = dfos.getFile();
550 
551         if (outputFile != null && outputFile.exists()) {
552             outputFile.delete();
553         }
554     }
555 
556     /**
557      * Creates and returns a {@link java.io.File File} representing a uniquely
558      * named temporary file in the configured repository path. The lifetime of
559      * the file is tied to the lifetime of the <code>FileItem</code> instance;
560      * the file will be deleted when the instance is garbage collected.
561      * <p>
562      * <b>Note: Subclasses that override this method must ensure that they return the
563      * same File each time.</b>
564      *
565      * @return The {@link java.io.File File} to be used for temporary storage.
566      */
567     protected File getTempFile() {
568         if (tempFile == null) {
569             File tempDir = repository;
570             if (tempDir == null) {
571                 tempDir = new File(System.getProperty("java.io.tmpdir"));
572             }
573 
574             String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
575 
576             tempFile = new File(tempDir, tempFileName);
577         }
578         return tempFile;
579     }
580 
581     // -------------------------------------------------------- Private methods
582 
583     /**
584      * Returns an identifier that is unique within the class loader used to
585      * load this class, but does not have random-like apearance.
586      *
587      * @return A String with the non-random looking instance identifier.
588      */
589     private static String getUniqueId() {
590         final int limit = 100000000;
591         int current = COUNTER.getAndIncrement();
592         String id = Integer.toString(current);
593 
594         // If you manage to get more than 100 million of ids, you'll
595         // start getting ids longer than 8 characters.
596         if (current < limit) {
597             id = ("00000000" + id).substring(id.length());
598         }
599         return id;
600     }
601 
602     /**
603      * Returns a string representation of this object.
604      *
605      * @return a string representation of this object.
606      */
607     @Override
608     public String toString() {
609         return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
610                       getName(), getStoreLocation(), Long.valueOf(getSize()),
611                       Boolean.valueOf(isFormField()), getFieldName());
612     }
613 
614     // -------------------------------------------------- Serialization methods
615 
616     /**
617      * Writes the state of this object during serialization.
618      *
619      * @param out The stream to which the state should be written.
620      *
621      * @throws IOException if an error occurs.
622      */
623     private void writeObject(ObjectOutputStream out) throws IOException {
624         // Read the data
625         if (dfos.isInMemory()) {
626             cachedContent = get();
627         } else {
628             cachedContent = null;
629             dfosFile = dfos.getFile();
630         }
631 
632         // write out values
633         out.defaultWriteObject();
634     }
635 
636     /**
637      * Reads the state of this object during deserialization.
638      *
639      * @param in The stream from which the state should be read.
640      *
641      * @throws IOException if an error occurs.
642      * @throws ClassNotFoundException if class cannot be found.
643      */
644     private void readObject(ObjectInputStream in)
645             throws IOException, ClassNotFoundException {
646         // read values
647         in.defaultReadObject();
648 
649         /* One expected use of serialization is to migrate HTTP sessions
650          * containing a DiskFileItem between JVMs. Particularly if the JVMs are
651          * on different machines It is possible that the repository location is
652          * not valid so validate it.
653          */
654         if (repository != null) {
655             if (repository.isDirectory()) {
656                 // Check path for nulls
657                 if (repository.getPath().contains("\0")) {
658                     throw new IOException(format(
659                             "The repository [%s] contains a null character",
660                             repository.getPath()));
661                 }
662             } else {
663                 throw new IOException(format(
664                         "The repository [%s] is not a directory",
665                         repository.getAbsolutePath()));
666             }
667         }
668 
669         OutputStream output = getOutputStream();
670         if (cachedContent != null) {
671             output.write(cachedContent);
672         } else {
673             FileInputStream input = new FileInputStream(dfosFile);
674             IOUtils.copy(input, output);
675             input.close();
676             dfosFile.delete();
677             dfosFile = null;
678         }
679         output.close();
680 
681         cachedContent = null;
682     }
683 
684     /**
685      * Returns the file item headers.
686      * @return The file items headers.
687      */
688     public FileItemHeaders getHeaders() {
689         return headers;
690     }
691 
692     /**
693      * Sets the file item headers.
694      * @param pHeaders The file items headers.
695      */
696     public void setHeaders(FileItemHeaders pHeaders) {
697         headers = pHeaders;
698     }
699 
700 }