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.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.OutputStream;
28 import java.io.UnsupportedEncodingException;
29 import java.util.Map;
30 import java.util.UUID;
31 import java.util.concurrent.atomic.AtomicInteger;
32
33 import org.apache.commons.fileupload.FileItem;
34 import org.apache.commons.fileupload.FileItemHeaders;
35 import org.apache.commons.fileupload.FileUploadException;
36 import org.apache.commons.fileupload.ParameterParser;
37 import org.apache.commons.fileupload.util.Streams;
38 import org.apache.commons.io.FileUtils;
39 import org.apache.commons.io.IOUtils;
40 import org.apache.commons.io.output.DeferredFileOutputStream;
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 * DiskFileItemFactory} instance (see
48 * {@link org.apache.commons.fileupload.servlet.ServletFileUpload
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>Temporary files, which are created for file items, should be
56 * deleted later on. The best way to do this is using a
57 * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the
58 * {@link DiskFileItemFactory}. However, if you do use such a tracker,
59 * then you must consider the following: Temporary files are automatically
60 * deleted as soon as they are no longer needed. (More precisely, when the
61 * corresponding instance of {@link java.io.File} is garbage collected.)
62 * This is done by the so-called reaper thread, which is started and stopped
63 * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when
64 * there are files to be tracked.
65 * It might make sense to terminate that thread, for example, if
66 * your web application ends. See the section on "Resource cleanup"
67 * in the users guide of commons-fileupload.</p>
68 *
69 * @since FileUpload 1.1
70 */
71 public class DiskFileItem
72 implements FileItem {
73
74 // ----------------------------------------------------- Manifest constants
75
76 /**
77 * Default content charset to be used when no explicit charset
78 * parameter is provided by the sender. Media subtypes of the
79 * "text" type are defined to have a default charset value of
80 * "ISO-8859-1" when received via HTTP.
81 */
82 public static final String DEFAULT_CHARSET = "ISO-8859-1";
83
84 // ----------------------------------------------------------- Data members
85
86 /**
87 * UID used in unique file name generation.
88 */
89 private static final String UID =
90 UUID.randomUUID().toString().replace('-', '_');
91
92 /**
93 * Counter used in unique identifier generation.
94 */
95 private static final AtomicInteger COUNTER = new AtomicInteger(0);
96
97 /**
98 * The name of the form field as provided by the browser.
99 */
100 private String fieldName;
101
102 /**
103 * The content type passed by the browser, or <code>null</code> if
104 * not defined.
105 */
106 private final String contentType;
107
108 /**
109 * Whether or not this item is a simple form field.
110 */
111 private boolean isFormField;
112
113 /**
114 * The original filename in the user's filesystem.
115 */
116 private final String fileName;
117
118 /**
119 * The size of the item, in bytes. This is used to cache the size when a
120 * file item is moved from its original location.
121 */
122 private long size = -1;
123
124
125 /**
126 * The threshold above which uploads will be stored on disk.
127 */
128 private final int sizeThreshold;
129
130 /**
131 * The directory in which uploaded files will be stored, if stored on disk.
132 */
133 private final File repository;
134
135 /**
136 * Cached contents of the file.
137 */
138 private byte[] cachedContent;
139
140 /**
141 * Output stream for this item.
142 */
143 private transient DeferredFileOutputStream dfos;
144
145 /**
146 * The temporary file to use.
147 */
148 private transient File tempFile;
149
150 /**
151 * The file items headers.
152 */
153 private FileItemHeaders headers;
154
155 // ----------------------------------------------------------- Constructors
156
157 /**
158 * Constructs a new <code>DiskFileItem</code> instance.
159 *
160 * @param fieldName The name of the form field.
161 * @param contentType The content type passed by the browser or
162 * <code>null</code> if not specified.
163 * @param isFormField Whether or not this item is a plain form field, as
164 * opposed to a file upload.
165 * @param fileName The original filename in the user's filesystem, or
166 * <code>null</code> if not specified.
167 * @param sizeThreshold The threshold, in bytes, below which items will be
168 * retained in memory and above which they will be
169 * stored as a file.
170 * @param repository The data repository, which is the directory in
171 * which files will be created, should the item size
172 * exceed the threshold.
173 */
174 public DiskFileItem(String fieldName,
175 String contentType, boolean isFormField, String fileName,
176 int sizeThreshold, File repository) {
177 this.fieldName = fieldName;
178 this.contentType = contentType;
179 this.isFormField = isFormField;
180 this.fileName = fileName;
181 this.sizeThreshold = sizeThreshold;
182 this.repository = repository;
183 }
184
185 // ------------------------------- Methods from javax.activation.DataSource
186
187 /**
188 * Returns an {@link java.io.InputStream InputStream} that can be
189 * used to retrieve the contents of the file.
190 *
191 * @return An {@link java.io.InputStream InputStream} that can be
192 * used to retrieve the contents of the file.
193 *
194 * @throws IOException if an error occurs.
195 */
196 public InputStream getInputStream()
197 throws IOException {
198 if (!isInMemory()) {
199 return new FileInputStream(dfos.getFile());
200 }
201
202 if (cachedContent == null) {
203 cachedContent = dfos.getData();
204 }
205 return new ByteArrayInputStream(cachedContent);
206 }
207
208 /**
209 * Returns the content type passed by the agent or <code>null</code> if
210 * not defined.
211 *
212 * @return The content type passed by the agent or <code>null</code> if
213 * not defined.
214 */
215 public String getContentType() {
216 return contentType;
217 }
218
219 /**
220 * Returns the content charset passed by the agent or <code>null</code> if
221 * not defined.
222 *
223 * @return The content charset passed by the agent or <code>null</code> if
224 * not defined.
225 */
226 public String getCharSet() {
227 ParameterParser parser = new ParameterParser();
228 parser.setLowerCaseNames(true);
229 // Parameter parser can handle null input
230 Map<String, String> params = parser.parse(getContentType(), ';');
231 return params.get("charset");
232 }
233
234 /**
235 * Returns the original filename in the client's filesystem.
236 *
237 * @return The original filename in the client's filesystem.
238 * @throws org.apache.commons.fileupload.InvalidFileNameException The file name contains a NUL character,
239 * which might be an indicator of a security attack. If you intend to
240 * use the file name anyways, catch the exception and use
241 * {@link org.apache.commons.fileupload.InvalidFileNameException#getName()}.
242 */
243 public String getName() {
244 return Streams.checkFileName(fileName);
245 }
246
247 // ------------------------------------------------------- FileItem methods
248
249 /**
250 * Provides a hint as to whether or not the file contents will be read
251 * from memory.
252 *
253 * @return <code>true</code> if the file contents will be read
254 * from memory; <code>false</code> otherwise.
255 */
256 public boolean isInMemory() {
257 if (cachedContent != null) {
258 return true;
259 }
260 return dfos.isInMemory();
261 }
262
263 /**
264 * Returns the size of the file.
265 *
266 * @return The size of the file, in bytes.
267 */
268 public long getSize() {
269 if (size >= 0) {
270 return size;
271 } else if (cachedContent != null) {
272 return cachedContent.length;
273 } else if (dfos.isInMemory()) {
274 return dfos.getData().length;
275 } else {
276 return dfos.getFile().length();
277 }
278 }
279
280 /**
281 * Returns the contents of the file as an array of bytes. If the
282 * contents of the file were not yet cached in memory, they will be
283 * loaded from the disk storage and cached.
284 *
285 * @return The contents of the file as an array of bytes
286 * or {@code null} if the data cannot be read
287 */
288 public byte[] get() {
289 if (isInMemory()) {
290 if (cachedContent == null && dfos != null) {
291 cachedContent = dfos.getData();
292 }
293 return cachedContent;
294 }
295
296 byte[] fileData = new byte[(int) getSize()];
297 InputStream fis = null;
298
299 try {
300 fis = new FileInputStream(dfos.getFile());
301 IOUtils.readFully(fis, fileData);
302 } catch (IOException e) {
303 fileData = null;
304 } finally {
305 IOUtils.closeQuietly(fis);
306 }
307
308 return fileData;
309 }
310
311 /**
312 * Returns the contents of the file as a String, using the specified
313 * encoding. This method uses {@link #get()} to retrieve the
314 * contents of the file.
315 *
316 * @param charset The charset to use.
317 *
318 * @return The contents of the file, as a string.
319 *
320 * @throws UnsupportedEncodingException if the requested character
321 * encoding is not available.
322 */
323 public String getString(final String charset)
324 throws UnsupportedEncodingException {
325 return new String(get(), charset);
326 }
327
328 /**
329 * Returns the contents of the file as a String, using the default
330 * character encoding. This method uses {@link #get()} to retrieve the
331 * contents of the file.
332 *
333 * <b>TODO</b> Consider making this method throw UnsupportedEncodingException.
334 *
335 * @return The contents of the file, as a string.
336 */
337 public String getString() {
338 byte[] rawdata = get();
339 String charset = getCharSet();
340 if (charset == null) {
341 charset = DEFAULT_CHARSET;
342 }
343 try {
344 return new String(rawdata, charset);
345 } catch (UnsupportedEncodingException e) {
346 return new String(rawdata);
347 }
348 }
349
350 /**
351 * A convenience method to write an uploaded item to disk. The client code
352 * is not concerned with whether or not the item is stored in memory, or on
353 * disk in a temporary location. They just want to write the uploaded item
354 * to a file.
355 * <p>
356 * This implementation first attempts to rename the uploaded item to the
357 * specified destination file, if the item was originally written to disk.
358 * Otherwise, the data will be copied to the specified file.
359 * <p>
360 * This method is only guaranteed to work <em>once</em>, the first time it
361 * is invoked for a particular item. This is because, in the event that the
362 * method renames a temporary file, that file will no longer be available
363 * to copy or rename again at a later time.
364 *
365 * @param file The <code>File</code> into which the uploaded item should
366 * be stored.
367 *
368 * @throws Exception if an error occurs.
369 */
370 public void write(File file) throws Exception {
371 if (isInMemory()) {
372 FileOutputStream fout = null;
373 try {
374 fout = new FileOutputStream(file);
375 fout.write(get());
376 fout.close();
377 } finally {
378 IOUtils.closeQuietly(fout);
379 }
380 } else {
381 File outputFile = getStoreLocation();
382 if (outputFile != null) {
383 // Save the length of the file
384 size = outputFile.length();
385 /*
386 * The uploaded file is being stored on disk
387 * in a temporary location so move it to the
388 * desired file.
389 */
390 FileUtils.moveFile(outputFile, file);
391 } else {
392 /*
393 * For whatever reason we cannot write the
394 * file to disk.
395 */
396 throw new FileUploadException(
397 "Cannot write uploaded file to disk!");
398 }
399 }
400 }
401
402 /**
403 * Deletes the underlying storage for a file item, including deleting any
404 * associated temporary disk file. Although this storage will be deleted
405 * automatically when the <code>FileItem</code> instance is garbage
406 * collected, this method can be used to ensure that this is done at an
407 * earlier time, thus preserving system resources.
408 */
409 public void delete() {
410 cachedContent = null;
411 File outputFile = getStoreLocation();
412 if (outputFile != null && outputFile.exists()) {
413 outputFile.delete();
414 }
415 }
416
417 /**
418 * Returns the name of the field in the multipart form corresponding to
419 * this file item.
420 *
421 * @return The name of the form field.
422 *
423 * @see #setFieldName(java.lang.String)
424 *
425 */
426 public String getFieldName() {
427 return fieldName;
428 }
429
430 /**
431 * Sets the field name used to reference this file item.
432 *
433 * @param fieldName The name of the form field.
434 *
435 * @see #getFieldName()
436 *
437 */
438 public void setFieldName(String fieldName) {
439 this.fieldName = fieldName;
440 }
441
442 /**
443 * Determines whether or not a <code>FileItem</code> instance represents
444 * a simple form field.
445 *
446 * @return <code>true</code> if the instance represents a simple form
447 * field; <code>false</code> if it represents an uploaded file.
448 *
449 * @see #setFormField(boolean)
450 *
451 */
452 public boolean isFormField() {
453 return isFormField;
454 }
455
456 /**
457 * Specifies whether or not a <code>FileItem</code> instance represents
458 * a simple form field.
459 *
460 * @param state <code>true</code> if the instance represents a simple form
461 * field; <code>false</code> if it represents an uploaded file.
462 *
463 * @see #isFormField()
464 *
465 */
466 public void setFormField(boolean state) {
467 isFormField = state;
468 }
469
470 /**
471 * Returns an {@link java.io.OutputStream OutputStream} that can
472 * be used for storing the contents of the file.
473 *
474 * @return An {@link java.io.OutputStream OutputStream} that can be used
475 * for storing the contents of the file.
476 *
477 * @throws IOException if an error occurs.
478 */
479 public OutputStream getOutputStream()
480 throws IOException {
481 if (dfos == null) {
482 File outputFile = getTempFile();
483 dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
484 }
485 return dfos;
486 }
487
488 // --------------------------------------------------------- Public methods
489
490 /**
491 * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
492 * data's temporary location on the disk. Note that for
493 * <code>FileItem</code>s that have their data stored in memory,
494 * this method will return <code>null</code>. When handling large
495 * files, you can use {@link java.io.File#renameTo(java.io.File)} to
496 * move the file to new location without copying the data, if the
497 * source and destination locations reside within the same logical
498 * volume.
499 *
500 * @return The data file, or <code>null</code> if the data is stored in
501 * memory.
502 */
503 public File getStoreLocation() {
504 if (dfos == null) {
505 return null;
506 }
507 if (isInMemory()) {
508 return null;
509 }
510 return dfos.getFile();
511 }
512
513 // ------------------------------------------------------ Protected methods
514
515 /**
516 * Removes the file contents from the temporary storage.
517 */
518 @Override
519 protected void finalize() {
520 if (dfos == null) {
521 return;
522 }
523 File outputFile = dfos.getFile();
524
525 if (outputFile != null && outputFile.exists()) {
526 outputFile.delete();
527 }
528 }
529
530 /**
531 * Creates and returns a {@link java.io.File File} representing a uniquely
532 * named temporary file in the configured repository path. The lifetime of
533 * the file is tied to the lifetime of the <code>FileItem</code> instance;
534 * the file will be deleted when the instance is garbage collected.
535 * <p>
536 * <b>Note: Subclasses that override this method must ensure that they return the
537 * same File each time.</b>
538 *
539 * @return The {@link java.io.File File} to be used for temporary storage.
540 */
541 protected File getTempFile() {
542 if (tempFile == null) {
543 File tempDir = repository;
544 if (tempDir == null) {
545 tempDir = new File(System.getProperty("java.io.tmpdir"));
546 }
547
548 String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
549
550 tempFile = new File(tempDir, tempFileName);
551 }
552 return tempFile;
553 }
554
555 // -------------------------------------------------------- Private methods
556
557 /**
558 * Returns an identifier that is unique within the class loader used to
559 * load this class, but does not have random-like appearance.
560 *
561 * @return A String with the non-random looking instance identifier.
562 */
563 private static String getUniqueId() {
564 final int limit = 100000000;
565 int current = COUNTER.getAndIncrement();
566 String id = Integer.toString(current);
567
568 // If you manage to get more than 100 million of ids, you'll
569 // start getting ids longer than 8 characters.
570 if (current < limit) {
571 id = ("00000000" + id).substring(id.length());
572 }
573 return id;
574 }
575
576 /**
577 * Returns a string representation of this object.
578 *
579 * @return a string representation of this object.
580 */
581 @Override
582 public String toString() {
583 return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
584 getName(), getStoreLocation(), Long.valueOf(getSize()),
585 Boolean.valueOf(isFormField()), getFieldName());
586 }
587
588 /**
589 * Returns the file item headers.
590 * @return The file items headers.
591 */
592 public FileItemHeaders getHeaders() {
593 return headers;
594 }
595
596 /**
597 * Sets the file item headers.
598 * @param pHeaders The file items headers.
599 */
600 public void setHeaders(FileItemHeaders pHeaders) {
601 headers = pHeaders;
602 }
603
604 }