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.ParameterParser;
38  import org.apache.commons.io.IOUtils;
39  import org.apache.commons.io.output.DeferredFileOutputStream;
40  
41  
42  /**
43   * <p> The default implementation of the
44   * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
45   *
46   * <p> After retrieving an instance of this class from a {@link
47   * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
48   * {@link org.apache.commons.fileupload.DiskFileUpload
49   * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
50   * either request all contents of file at once using {@link #get()} or
51   * request an {@link java.io.InputStream InputStream} with
52   * {@link #getInputStream()} and process the file without attempting to load
53   * it into memory, which may come handy with large files.
54   *
55   * <p>When using the <code>DiskFileItemFactory</code>, then you should
56   * consider the following: Temporary files are automatically deleted as
57   * soon as they are no longer needed. (More precisely, when the
58   * corresponding instance of {@link java.io.File} is garbage collected.)
59   * This is done by the so-called reaper thread, which is started
60   * automatically when the class {@link org.apache.commons.io.FileCleaner}
61   * is loaded.
62   * It might make sense to terminate that thread, for example, if
63   * your web application ends. See the section on "Resource cleanup"
64   * in the users guide of commons-fileupload.</p>
65   *
66   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
67   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
68   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
69   * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
70   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
71   * @author Sean C. Sullivan
72   *
73   * @since FileUpload 1.1
74   *
75   * @version $Id: DiskFileItem.java 607869 2008-01-01 16:42:17Z jochen $
76   */
77  public class DiskFileItem
78      implements FileItem, FileItemHeadersSupport {
79  
80      // ----------------------------------------------------- Manifest constants
81  
82      /**
83       * The UID to use when serializing this instance.
84       */
85      private static final long serialVersionUID = 2237570099615271025L;
86  
87  
88      /**
89       * Default content charset to be used when no explicit charset
90       * parameter is provided by the sender. Media subtypes of the
91       * "text" type are defined to have a default charset value of
92       * "ISO-8859-1" when received via HTTP.
93       */
94      public static final String DEFAULT_CHARSET = "ISO-8859-1";
95  
96  
97      // ----------------------------------------------------------- Data members
98  
99  
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 }