View Javadoc

1   /*
2    * Copyright 2001-2005 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.fileupload.disk;
17  
18  import java.io.BufferedInputStream;
19  import java.io.BufferedOutputStream;
20  import java.io.ByteArrayInputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.io.ObjectOutputStream;
28  import java.io.ObjectInputStream;
29  import java.io.UnsupportedEncodingException;
30  import java.rmi.server.UID;
31  import java.util.Map;
32  import org.apache.commons.io.IOUtils;
33  import org.apache.commons.io.FileCleaner;
34  import org.apache.commons.io.output.DeferredFileOutputStream;
35  
36  import org.apache.commons.fileupload.FileItem;
37  import org.apache.commons.fileupload.FileUploadException;
38  import org.apache.commons.fileupload.ParameterParser;
39  
40  
41  /***
42   * <p> The default implementation of the
43   * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
44   *
45   * <p> After retrieving an instance of this class from a {@link
46   * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
47   * {@link org.apache.commons.fileupload.DiskFileUpload
48   * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
49   * either request all contents of file at once using {@link #get()} or
50   * request an {@link java.io.InputStream InputStream} with
51   * {@link #getInputStream()} and process the file without attempting to load
52   * it into memory, which may come handy with large files.
53   *
54   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
55   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
56   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
57   * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
58   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
59   * @author Sean C. Sullivan
60   *
61   * @since FileUpload 1.1
62   *
63   * @version $Id: DiskFileItem.java 399546 2006-05-04 04:53:30Z martinc $
64   */
65  public class DiskFileItem
66      implements FileItem {
67  
68      // ----------------------------------------------------- Manifest constants
69  
70  
71      /***
72       * Default content charset to be used when no explicit charset
73       * parameter is provided by the sender. Media subtypes of the
74       * "text" type are defined to have a default charset value of
75       * "ISO-8859-1" when received via HTTP.
76       */
77      public static final String DEFAULT_CHARSET = "ISO-8859-1";
78  
79  
80      // ----------------------------------------------------------- Data members
81  
82  
83      /***
84       * UID used in unique file name generation.
85       */
86      private static final String UID =
87              new UID().toString().replace(':', '_').replace('-', '_');
88  
89      /***
90       * Counter used in unique identifier generation.
91       */
92      private static int counter = 0;
93  
94  
95      /***
96       * The name of the form field as provided by the browser.
97       */
98      private String fieldName;
99  
100 
101     /***
102      * The content type passed by the browser, or <code>null</code> if
103      * not defined.
104      */
105     private String contentType;
106 
107 
108     /***
109      * Whether or not this item is a simple form field.
110      */
111     private boolean isFormField;
112 
113 
114     /***
115      * The original filename in the user's filesystem.
116      */
117     private String fileName;
118 
119 
120     /***
121      * The size of the item, in bytes. This is used to cache the size when a
122      * file item is moved from its original location.
123      */
124     private long size = -1;
125 
126 
127     /***
128      * The threshold above which uploads will be stored on disk.
129      */
130     private int sizeThreshold;
131 
132 
133     /***
134      * The directory in which uploaded files will be stored, if stored on disk.
135      */
136     private File repository;
137 
138 
139     /***
140      * Cached contents of the file.
141      */
142     private byte[] cachedContent;
143 
144 
145     /***
146      * Output stream for this item.
147      */
148     private transient DeferredFileOutputStream dfos;
149 
150     /***
151      * File to allow for serialization of the content of this item.
152      */
153     private File dfosFile;
154 
155 
156     // ----------------------------------------------------------- Constructors
157 
158 
159     /***
160      * Constructs a new <code>DiskFileItem</code> instance.
161      *
162      * @param fieldName     The name of the form field.
163      * @param contentType   The content type passed by the browser or
164      *                      <code>null</code> if not specified.
165      * @param isFormField   Whether or not this item is a plain form field, as
166      *                      opposed to a file upload.
167      * @param fileName      The original filename in the user's filesystem, or
168      *                      <code>null</code> if not specified.
169      * @param sizeThreshold The threshold, in bytes, below which items will be
170      *                      retained in memory and above which they will be
171      *                      stored as a file.
172      * @param repository    The data repository, which is the directory in
173      *                      which files will be created, should the item size
174      *                      exceed the threshold.
175      */
176     public DiskFileItem(String fieldName, String contentType,
177             boolean isFormField, String fileName, int sizeThreshold,
178             File repository) {
179         this.fieldName = fieldName;
180         this.contentType = contentType;
181         this.isFormField = isFormField;
182         this.fileName = fileName;
183         this.sizeThreshold = sizeThreshold;
184         this.repository = repository;
185     }
186 
187 
188     // ------------------------------- Methods from javax.activation.DataSource
189 
190 
191     /***
192      * Returns an {@link java.io.InputStream InputStream} that can be
193      * used to retrieve the contents of the file.
194      *
195      * @return An {@link java.io.InputStream InputStream} that can be
196      *         used to retrieve the contents of the file.
197      *
198      * @throws IOException if an error occurs.
199      */
200     public InputStream getInputStream()
201         throws IOException {
202         if (!isInMemory()) {
203             return new FileInputStream(dfos.getFile());
204         }
205 
206         if (cachedContent == null) {
207             cachedContent = dfos.getData();
208         }
209         return new ByteArrayInputStream(cachedContent);
210     }
211 
212 
213     /***
214      * Returns the content type passed by the agent or <code>null</code> if
215      * not defined.
216      *
217      * @return The content type passed by the agent or <code>null</code> if
218      *         not defined.
219      */
220     public String getContentType() {
221         return contentType;
222     }
223 
224 
225     /***
226      * Returns the content charset passed by the agent or <code>null</code> if
227      * not defined.
228      *
229      * @return The content charset passed by the agent or <code>null</code> if
230      *         not defined.
231      */
232     public String getCharSet() {
233         ParameterParser parser = new ParameterParser();
234         parser.setLowerCaseNames(true);
235         // Parameter parser can handle null input
236         Map params = parser.parse(getContentType(), ';');
237         return (String) params.get("charset");
238     }
239 
240 
241     /***
242      * Returns the original filename in the client's filesystem.
243      *
244      * @return The original filename in the client's filesystem.
245      */
246     public String getName() {
247         return fileName;
248     }
249 
250 
251     // ------------------------------------------------------- FileItem methods
252 
253 
254     /***
255      * Provides a hint as to whether or not the file contents will be read
256      * from memory.
257      *
258      * @return <code>true</code> if the file contents will be read
259      *         from memory; <code>false</code> otherwise.
260      */
261     public boolean isInMemory() {
262         if (cachedContent != null) {
263             return true;
264         } else {
265             return dfos.isInMemory();
266         }
267     }
268 
269 
270     /***
271      * Returns the size of the file.
272      *
273      * @return The size of the file, in bytes.
274      */
275     public long getSize() {
276         if (size >= 0) {
277             return size;
278         } else if (cachedContent != null) {
279             return cachedContent.length;
280         } else if (dfos.isInMemory()) {
281             return dfos.getData().length;
282         } else {
283             return dfos.getFile().length();
284         }
285     }
286 
287 
288     /***
289      * Returns the contents of the file as an array of bytes.  If the
290      * contents of the file were not yet cached in memory, they will be
291      * loaded from the disk storage and cached.
292      *
293      * @return The contents of the file as an array of bytes.
294      */
295     public byte[] get() {
296         if (isInMemory()) {
297             if (cachedContent == null) {
298                 cachedContent = dfos.getData();
299             }
300             return cachedContent;
301         }
302 
303         byte[] fileData = new byte[(int) getSize()];
304         FileInputStream fis = null;
305 
306         try {
307             fis = new FileInputStream(dfos.getFile());
308             fis.read(fileData);
309         } catch (IOException e) {
310             fileData = null;
311         } finally {
312             if (fis != null) {
313                 try {
314                     fis.close();
315                 } catch (IOException e) {
316                     // ignore
317                 }
318             }
319         }
320 
321         return fileData;
322     }
323 
324 
325     /***
326      * Returns the contents of the file as a String, using the specified
327      * encoding.  This method uses {@link #get()} to retrieve the
328      * contents of the file.
329      *
330      * @param charset The charset to use.
331      *
332      * @return The contents of the file, as a string.
333      *
334      * @throws UnsupportedEncodingException if the requested character
335      *                                      encoding is not available.
336      */
337     public String getString(final String charset)
338         throws UnsupportedEncodingException {
339         return new String(get(), charset);
340     }
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      * @return The contents of the file, as a string.
349      *
350      * @todo Consider making this method throw UnsupportedEncodingException.
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     /***
367      * A convenience method to write an uploaded item to disk. The client code
368      * is not concerned with whether or not the item is stored in memory, or on
369      * disk in a temporary location. They just want to write the uploaded item
370      * to a file.
371      * <p>
372      * This implementation first attempts to rename the uploaded item to the
373      * specified destination file, if the item was originally written to disk.
374      * Otherwise, the data will be copied to the specified file.
375      * <p>
376      * This method is only guaranteed to work <em>once</em>, the first time it
377      * is invoked for a particular item. This is because, in the event that the
378      * method renames a temporary file, that file will no longer be available
379      * to copy or rename again at a later time.
380      *
381      * @param file The <code>File</code> into which the uploaded item should
382      *             be stored.
383      *
384      * @throws Exception if an error occurs.
385      */
386     public void write(File file) throws Exception {
387         if (isInMemory()) {
388             FileOutputStream fout = null;
389             try {
390                 fout = new FileOutputStream(file);
391                 fout.write(get());
392             } finally {
393                 if (fout != null) {
394                     fout.close();
395                 }
396             }
397         } else {
398             File outputFile = getStoreLocation();
399             if (outputFile != null) {
400                 // Save the length of the file
401                 size = outputFile.length();
402                 /*
403                  * The uploaded file is being stored on disk
404                  * in a temporary location so move it to the
405                  * desired file.
406                  */
407                 if (!outputFile.renameTo(file)) {
408                     BufferedInputStream in = null;
409                     BufferedOutputStream out = null;
410                     try {
411                         in = new BufferedInputStream(
412                             new FileInputStream(outputFile));
413                         out = new BufferedOutputStream(
414                                 new FileOutputStream(file));
415                         IOUtils.copy(in, out);
416                     } finally {
417                         if (in != null) {
418                             try {
419                                 in.close();
420                             } catch (IOException e) {
421                                 // ignore
422                             }
423                         }
424                         if (out != null) {
425                             try {
426                                 out.close();
427                             } catch (IOException e) {
428                                 // ignore
429                             }
430                         }
431                     }
432                 }
433             } else {
434                 /*
435                  * For whatever reason we cannot write the
436                  * file to disk.
437                  */
438                 throw new FileUploadException(
439                     "Cannot write uploaded file to disk!");
440             }
441         }
442     }
443 
444 
445     /***
446      * Deletes the underlying storage for a file item, including deleting any
447      * associated temporary disk file. Although this storage will be deleted
448      * automatically when the <code>FileItem</code> instance is garbage
449      * collected, this method can be used to ensure that this is done at an
450      * earlier time, thus preserving system resources.
451      */
452     public void delete() {
453         cachedContent = null;
454         File outputFile = getStoreLocation();
455         if (outputFile != null && outputFile.exists()) {
456             outputFile.delete();
457         }
458     }
459 
460 
461     /***
462      * Returns the name of the field in the multipart form corresponding to
463      * this file item.
464      *
465      * @return The name of the form field.
466      *
467      * @see #setFieldName(java.lang.String)
468      *
469      */
470     public String getFieldName() {
471         return fieldName;
472     }
473 
474 
475     /***
476      * Sets the field name used to reference this file item.
477      *
478      * @param fieldName The name of the form field.
479      *
480      * @see #getFieldName()
481      *
482      */
483     public void setFieldName(String fieldName) {
484         this.fieldName = fieldName;
485     }
486 
487 
488     /***
489      * Determines whether or not a <code>FileItem</code> instance represents
490      * a simple form field.
491      *
492      * @return <code>true</code> if the instance represents a simple form
493      *         field; <code>false</code> if it represents an uploaded file.
494      *
495      * @see #setFormField(boolean)
496      *
497      */
498     public boolean isFormField() {
499         return isFormField;
500     }
501 
502 
503     /***
504      * Specifies whether or not a <code>FileItem</code> instance represents
505      * a simple form field.
506      *
507      * @param state <code>true</code> if the instance represents a simple form
508      *              field; <code>false</code> if it represents an uploaded file.
509      *
510      * @see #isFormField()
511      *
512      */
513     public void setFormField(boolean state) {
514         isFormField = state;
515     }
516 
517 
518     /***
519      * Returns an {@link java.io.OutputStream OutputStream} that can
520      * be used for storing the contents of the file.
521      *
522      * @return An {@link java.io.OutputStream OutputStream} that can be used
523      *         for storing the contensts of the file.
524      *
525      * @throws IOException if an error occurs.
526      */
527     public OutputStream getOutputStream()
528         throws IOException {
529         if (dfos == null) {
530             File outputFile = getTempFile();
531             dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
532         }
533         return dfos;
534     }
535 
536 
537     // --------------------------------------------------------- Public methods
538 
539 
540     /***
541      * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
542      * data's temporary location on the disk. Note that for
543      * <code>FileItem</code>s that have their data stored in memory,
544      * this method will return <code>null</code>. When handling large
545      * files, you can use {@link java.io.File#renameTo(java.io.File)} to
546      * move the file to new location without copying the data, if the
547      * source and destination locations reside within the same logical
548      * volume.
549      *
550      * @return The data file, or <code>null</code> if the data is stored in
551      *         memory.
552      */
553     public File getStoreLocation() {
554         return dfos.getFile();
555     }
556 
557 
558     // ------------------------------------------------------ Protected methods
559 
560 
561     /***
562      * Removes the file contents from the temporary storage.
563      */
564     protected void finalize() {
565         File outputFile = dfos.getFile();
566 
567         if (outputFile != null && outputFile.exists()) {
568             outputFile.delete();
569         }
570     }
571 
572 
573     /***
574      * Creates and returns a {@link java.io.File File} representing a uniquely
575      * named temporary file in the configured repository path. The lifetime of
576      * the file is tied to the lifetime of the <code>FileItem</code> instance;
577      * the file will be deleted when the instance is garbage collected.
578      *
579      * @return The {@link java.io.File File} to be used for temporary storage.
580      */
581     protected File getTempFile() {
582         File tempDir = repository;
583         if (tempDir == null) {
584             tempDir = new File(System.getProperty("java.io.tmpdir"));
585         }
586 
587         String fileName = "upload_" + UID + "_" + getUniqueId() + ".tmp";
588 
589         File f = new File(tempDir, fileName);
590         FileCleaner.track(f, this);
591         return f;
592     }
593 
594 
595     // -------------------------------------------------------- Private methods
596 
597 
598     /***
599      * Returns an identifier that is unique within the class loader used to
600      * load this class, but does not have random-like apearance.
601      *
602      * @return A String with the non-random looking instance identifier.
603      */
604     private static String getUniqueId() {
605         final int limit = 100000000;
606         int current;
607         synchronized (DiskFileItem.class) {
608             current = counter++;
609         }
610         String id = Integer.toString(current);
611 
612         // If you manage to get more than 100 million of ids, you'll
613         // start getting ids longer than 8 characters.
614         if (current < limit) {
615             id = ("00000000" + id).substring(id.length());
616         }
617         return id;
618     }
619 
620 
621 
622 
623     /***
624      * Returns a string representation of this object.
625      *
626      * @return a string representation of this object.
627      */
628     public String toString() {
629         return "name=" + this.getName()
630             + ", StoreLocation="
631             + String.valueOf(this.getStoreLocation())
632             + ", size="
633             + this.getSize()
634             + "bytes, "
635             + "isFormField=" + isFormField()
636             + ", FieldName="
637             + this.getFieldName();
638     }
639 
640 
641     // -------------------------------------------------- Serialization methods
642 
643 
644     /***
645      * Writes the state of this object during serialization.
646      *
647      * @param out The stream to which the state should be written.
648      *
649      * @throws IOException if an error occurs.
650      */
651     private void writeObject(ObjectOutputStream out) throws IOException {
652         // Read the data
653         if (dfos.isInMemory()) {
654             cachedContent = get();
655         } else {
656             cachedContent = null;
657             dfosFile = dfos.getFile();
658         }
659 
660         // write out values
661         out.defaultWriteObject();
662     }
663 
664     /***
665      * Reads the state of this object during deserialization.
666      *
667      * @param in The stream from which the state should be read.
668      *
669      * @throws IOException if an error occurs.
670      * @throws ClassNotFoundException if class cannot be found.
671      */
672     private void readObject(ObjectInputStream in)
673             throws IOException, ClassNotFoundException {
674         // read values
675         in.defaultReadObject();
676 
677         OutputStream output = getOutputStream();
678         if (cachedContent != null) {
679             output.write(cachedContent);
680         } else {
681             FileInputStream input = new FileInputStream(dfosFile);
682 
683             IOUtils.copy(input, output);
684             dfosFile.delete();
685             dfosFile = null;
686         }
687         output.close();
688 
689         cachedContent = null;
690     }
691 
692 }