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 java.io.BufferedInputStream;
20  import java.io.BufferedOutputStream;
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.ObjectInputStream;
28  import java.io.ObjectOutputStream;
29  import java.io.OutputStream;
30  import java.io.UnsupportedEncodingException;
31  import java.util.Map;
32  
33  import org.apache.commons.fileupload.FileItem;
34  import org.apache.commons.fileupload.FileItemHeaders;
35  import org.apache.commons.fileupload.FileItemHeadersSupport;
36  import org.apache.commons.fileupload.FileUploadException;
37  import org.apache.commons.fileupload.InvalidFileNameException;
38  import org.apache.commons.fileupload.ParameterParser;
39  import org.apache.commons.fileupload.util.Streams;
40  import org.apache.commons.io.FileCleaningTracker;
41  import org.apache.commons.io.IOUtils;
42  import org.apache.commons.io.output.DeferredFileOutputStream;
43  
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   * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
51   * {@link org.apache.commons.fileupload.DiskFileUpload
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 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
66   * automatically when the class {@link org.apache.commons.io.FileCleaner}
67   * is loaded.
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   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
73   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
74   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
75   * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
76   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
77   * @author Sean C. Sullivan
78   *
79   * @since FileUpload 1.1
80   *
81   * @version $Id: DiskFileItem.java 963609 2010-07-13 06:56:47Z jochen $
82   */
83  public class DiskFileItem
84      implements FileItem, FileItemHeadersSupport {
85  
86      // ----------------------------------------------------- Manifest constants
87  
88      /**
89       * The UID to use when serializing this instance.
90       */
91      private static final long serialVersionUID = 2237570099615271025L;
92  
93  
94      /**
95       * Default content charset to be used when no explicit charset
96       * parameter is provided by the sender. Media subtypes of the
97       * "text" type are defined to have a default charset value of
98       * "ISO-8859-1" when received via HTTP.
99       */
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 }