All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phloc.web.fileupload.io.DiskFileItem Maven / Gradle / Ivy

/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phloc.web.fileupload.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.phloc.commons.annotations.ReturnsMutableObject;
import com.phloc.commons.charset.CCharset;
import com.phloc.commons.charset.CharsetManager;
import com.phloc.commons.io.file.FileOperations;
import com.phloc.commons.io.file.FileUtils;
import com.phloc.commons.io.file.SimpleFileIO;
import com.phloc.commons.io.streams.NonBlockingByteArrayInputStream;
import com.phloc.commons.io.streams.StreamUtils;
import com.phloc.commons.state.ESuccess;
import com.phloc.commons.state.ISuccessIndicator;
import com.phloc.web.fileupload.FileUploadException;
import com.phloc.web.fileupload.IFileItem;
import com.phloc.web.fileupload.IFileItemHeaders;
import com.phloc.web.fileupload.IFileItemHeadersSupport;
import com.phloc.web.fileupload.ParameterParser;
import com.phloc.web.fileupload.util.Streams;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * 

* The default implementation of the {@link IFileItem} interface. *

* After retrieving an instance you may either request all contents of file at * once using {@link #get()} or request an {@link java.io.InputStream * InputStream} with {@link #getInputStream()} and process the file without * attempting to load it into memory, which may come handy with large files. * * @author Rafal Krzewski * @author Sean Legassick * @author Jason van Zyl * @author John McNally * @author Martin Cooper * @author Sean C. Sullivan * @since FileUpload 1.1 * @version $Id: DiskFileItem.java 963609 2010-07-13 06:56:47Z jochen $ */ public class DiskFileItem implements IFileItem, IFileItemHeadersSupport { // Because of transient field private static final long serialVersionUID = 1379943273879417L; /** * Default content charset to be used when no explicit charset parameter is * provided by the sender. Media subtypes of the "text" type are defined to * have a default charset value of "ISO-8859-1" when received via HTTP. */ public static final String DEFAULT_CHARSET = CCharset.CHARSET_ISO_8859_1; /** * Default content charset to be used when no explicit charset parameter is * provided by the sender. Media subtypes of the "text" type are defined to * have a default charset value of "ISO-8859-1" when received via HTTP. */ public static final Charset DEFAULT_CHARSET_OBJ = CCharset.CHARSET_ISO_8859_1_OBJ; /** * UID used in unique file name generation. */ private static final String UID = UUID.randomUUID ().toString ().replace (':', '_').replace ('-', '_'); /** * Counter used in unique identifier generation. */ private static final AtomicInteger s_nCounter = new AtomicInteger (0); /** * The name of the form field as provided by the browser. */ private String m_sFieldName; /** * The content type passed by the browser, or null if not * defined. */ private final String m_sContentType; /** * Whether or not this item is a simple form field. */ private boolean m_bIsFormField; /** * The original filename in the user's filesystem. */ private final String m_sFilename; /** * The size of the item, in bytes. This is used to cache the size when a file * item is moved from its original location. */ private long m_nSize = -1; /** * The threshold above which uploads will be stored on disk. */ private final int m_nSizeThreshold; /** * The directory in which uploaded files will be stored, if stored on disk. */ private final File m_aRepository; /** * Cached contents of the file. */ private byte [] m_aCachedContent; /** * Output stream for this item. */ private transient DeferredFileOutputStream m_aDfos; /** * The temporary file to use. */ private transient File m_aTempFile; /** * File to allow for serialization of the content of this item. */ private File m_aDfosFile; /** * The file items headers. */ private IFileItemHeaders m_aHeaders; // ----------------------------------------------------------- Constructors /** * Constructs a new DiskFileItem instance. * * @param fieldName * The name of the form field. * @param contentType * The content type passed by the browser or null if not * specified. * @param isFormField * Whether or not this item is a plain form field, as opposed to a file * upload. * @param fileName * The original filename in the user's filesystem, or null * if not specified. * @param sizeThreshold * The threshold, in bytes, below which items will be retained in * memory and above which they will be stored as a file. * @param repository * The data repository, which is the directory in which files will be * created, should the item size exceed the threshold. */ public DiskFileItem (final String fieldName, @Nullable final String contentType, final boolean isFormField, @Nullable final String fileName, final int sizeThreshold, @Nullable final File repository) { m_sFieldName = fieldName; m_sContentType = contentType; m_bIsFormField = isFormField; m_sFilename = fileName; m_nSizeThreshold = sizeThreshold; m_aRepository = repository; } // ------------------------------- Methods from javax.activation.DataSource /** * Returns an {@link java.io.InputStream InputStream} that can be used to * retrieve the contents of the file. * * @return An {@link java.io.InputStream InputStream} that can be used to * retrieve the contents of the file. */ @Nonnull public InputStream getInputStream () { if (!isInMemory ()) return FileUtils.getInputStream (m_aDfos.getFile ()); if (m_aCachedContent == null) m_aCachedContent = m_aDfos.getData (); return new NonBlockingByteArrayInputStream (m_aCachedContent); } /** * Returns the content type passed by the agent or null if not * defined. * * @return The content type passed by the agent or null if not * defined. */ @Nullable public String getContentType () { return m_sContentType; } /** * Returns the content charset passed by the agent or null if not * defined. * * @return The content charset passed by the agent or null if not * defined. */ @Nullable public String getCharSet () { final ParameterParser parser = new ParameterParser (); parser.setLowerCaseNames (true); // Parameter parser can handle null input final Map params = parser.parse (getContentType (), ';'); return params.get ("charset"); } /** * Returns the original filename in the client's filesystem. * * @return The original filename in the client's filesystem. * @throws com.phloc.web.fileupload.InvalidFileNameException * The file name contains a NUL character, which might be an indicator * of a security attack. If you intend to use the file name anyways, * catch the exception and use InvalidFileNameException#getName(). */ @Nullable public String getName () { return Streams.checkFileName (m_sFilename); } /** * Provides a hint as to whether or not the file contents will be read from * memory. * * @return true if the file contents will be read from memory; * false otherwise. */ public boolean isInMemory () { return m_aCachedContent != null || m_aDfos.isInMemory (); } /** * Returns the size of the file. * * @return The size of the file, in bytes. */ @Nonnegative public long getSize () { if (m_nSize >= 0) return m_nSize; if (m_aCachedContent != null) return m_aCachedContent.length; if (m_aDfos.isInMemory ()) return m_aDfos.getData ().length; return m_aDfos.getFile ().length (); } /** * Returns the contents of the file as an array of bytes. If the contents of * the file were not yet cached in memory, they will be loaded from the disk * storage and cached. * * @return The contents of the file as an array of bytes. */ @ReturnsMutableObject (reason = "Speed") @SuppressFBWarnings ("EI_EXPOSE_REP") @Nullable public byte [] get () { if (isInMemory ()) { if (m_aCachedContent == null) m_aCachedContent = m_aDfos.getData (); return m_aCachedContent; } return SimpleFileIO.readFileBytes (m_aDfos.getFile ()); } /** * Returns the contents of the file as a String, using the specified encoding. * This method uses {@link #get()} to retrieve the contents of the file. * * @param charset * The charset to use. * @return The contents of the file, as a string. * @throws UnsupportedEncodingException * if the requested character encoding is not available. */ @Nonnull public String getString (final String charset) throws UnsupportedEncodingException { return new String (get (), charset); } /** * Returns the contents of the file as a String, using the specified encoding. * This method uses {@link #get()} to retrieve the contents of the file. * * @param aCharset * The charset to use. * @return The contents of the file, as a string. */ @Nonnull public String getString (@Nonnull final Charset aCharset) { return new String (get (), aCharset); } /** * Returns the contents of the file as a String, using the default character * encoding. This method uses {@link #get()} to retrieve the contents of the * file. * * @return The contents of the file, as a string. */ @Nonnull public String getString () { final byte [] rawdata = get (); final String charset = getCharSet (); final Charset aCharset = charset == null ? DEFAULT_CHARSET_OBJ : CharsetManager.getCharsetFromName (charset); return CharsetManager.getAsString (rawdata, aCharset); } /** * A convenience method to write an uploaded item to disk. The client code is * not concerned with whether or not the item is stored in memory, or on disk * in a temporary location. They just want to write the uploaded item to a * file. *

* This implementation first attempts to rename the uploaded item to the * specified destination file, if the item was originally written to disk. * Otherwise, the data will be copied to the specified file. *

* This method is only guaranteed to work once, the first time it is * invoked for a particular item. This is because, in the event that the * method renames a temporary file, that file will no longer be available to * copy or rename again at a later time. * * @param aFile * The File into which the uploaded item should be stored. * @throws FileUploadException * if an error occurs. */ @Nonnull public ISuccessIndicator write (@Nonnull final File aFile) throws FileUploadException { if (isInMemory ()) return SimpleFileIO.writeFile (aFile, get ()); final File aOutputFile = getStoreLocation (); if (aOutputFile != null) { // Save the length of the file m_nSize = aOutputFile.length (); /* * The uploaded file is being stored on disk in a temporary location so * move it to the desired file. */ if (FileOperations.renameFile (aOutputFile, aFile).isSuccess ()) return ESuccess.SUCCESS; // Copying needed return FileOperations.copyFile (aOutputFile, aFile); } // For whatever reason we cannot write the file to disk. throw new FileUploadException ("Cannot write uploaded file to disk!"); } /** * Deletes the underlying storage for a file item, including deleting any * associated temporary disk file. Although this storage will be deleted * automatically when the FileItem instance is garbage collected, * this method can be used to ensure that this is done at an earlier time, * thus preserving system resources. */ public void delete () { m_aCachedContent = null; final File outputFile = getStoreLocation (); if (outputFile != null && outputFile.exists ()) FileOperations.deleteFile (outputFile); } /** * Returns the name of the field in the multipart form corresponding to this * file item. * * @return The name of the form field. * @see #setFieldName(java.lang.String) */ public String getFieldName () { return m_sFieldName; } /** * Sets the field name used to reference this file item. * * @param fieldName * The name of the form field. * @see #getFieldName() */ public void setFieldName (final String fieldName) { m_sFieldName = fieldName; } /** * Determines whether or not a FileItem instance represents a * simple form field. * * @return true if the instance represents a simple form field; * false if it represents an uploaded file. * @see #setFormField(boolean) */ public boolean isFormField () { return m_bIsFormField; } /** * Specifies whether or not a FileItem instance represents a * simple form field. * * @param state * true if the instance represents a simple form field; * false if it represents an uploaded file. * @see #isFormField() */ public void setFormField (final boolean state) { m_bIsFormField = state; } /** * Returns an {@link java.io.OutputStream OutputStream} that can be used for * storing the contents of the file. * * @return An {@link java.io.OutputStream OutputStream} that can be used for * storing the contents of the file. */ @Nonnull public DeferredFileOutputStream getOutputStream () { if (m_aDfos == null) { final File outputFile = getTempFile (); m_aDfos = new DeferredFileOutputStream (m_nSizeThreshold, outputFile); } return m_aDfos; } // --------------------------------------------------------- Public methods /** * Returns the {@link java.io.File} object for the FileItem's * data's temporary location on the disk. Note that for FileItems * that have their data stored in memory, this method will return * null. When handling large files, you can use * {@link java.io.File#renameTo(java.io.File)} to move the file to new * location without copying the data, if the source and destination locations * reside within the same logical volume. * * @return The data file, or null if the data is stored in * memory. */ @Nullable public File getStoreLocation () { return m_aDfos == null ? null : m_aDfos.getFile (); } // ------------------------------------------------------ Protected methods /** * Removes the file contents from the temporary storage. * * @throws Throwable * as declared by super.finalize() */ @Override protected void finalize () throws Throwable { final File outputFile = m_aDfos.getFile (); if (outputFile != null && outputFile.exists ()) FileOperations.deleteFile (outputFile); super.finalize (); } /** * Creates and returns a {@link java.io.File File} representing a uniquely * named temporary file in the configured repository path. The lifetime of the * file is tied to the lifetime of the FileItem instance; the * file will be deleted when the instance is garbage collected. * * @return The {@link java.io.File File} to be used for temporary storage. */ @Nonnull protected File getTempFile () { if (m_aTempFile == null) { File tempDir = m_aRepository; if (tempDir == null) tempDir = new File (System.getProperty ("java.io.tmpdir")); final String tempFileName = "upload_" + UID + "_" + _getUniqueId () + ".tmp"; m_aTempFile = new File (tempDir, tempFileName); } return m_aTempFile; } // -------------------------------------------------------- Private methods /** * Returns an identifier that is unique within the class loader used to load * this class, but does not have random-like apearance. * * @return A String with the non-random looking instance identifier. */ @Nonnull private static String _getUniqueId () { final int limit = 100000000; final int current = s_nCounter.getAndIncrement (); String id = Integer.toString (current); // If you manage to get more than 100 million of ids, you'll // start getting ids longer than 8 characters. if (current < limit) { id = ("00000000" + id).substring (id.length ()); } return id; } /** * Returns a string representation of this object. * * @return a string representation of this object. */ @Override public String toString () { // avoid information disclosure about the server file system in case toString ends up in a response (e.g. on redirect when requesting a file item with expired session) File aStoreLocation = getStoreLocation (); String sStoreLocation = aStoreLocation == null ? null : aStoreLocation.getName (); return "name=" + getName () + ", StoreLocation=" + sStoreLocation + ", size=" + getSize () + "bytes, " + "isFormField=" + isFormField () + ", FieldName=" + getFieldName (); } // -------------------------------------------------- Serialization methods /** * Writes the state of this object during serialization. * * @param out * The stream to which the state should be written. * @throws IOException * if an error occurs. */ private void writeObject (final ObjectOutputStream out) throws IOException { // Read the data if (m_aDfos.isInMemory ()) { m_aCachedContent = get (); } else { m_aCachedContent = null; m_aDfosFile = m_aDfos.getFile (); } // write out values out.defaultWriteObject (); } /** * Reads the state of this object during deserialization. * * @param in * The stream from which the state should be read. * @throws IOException * if an error occurs. * @throws ClassNotFoundException * if class cannot be found. */ private void readObject (final ObjectInputStream in) throws IOException, ClassNotFoundException { // read values in.defaultReadObject (); final OutputStream output = getOutputStream (); if (m_aCachedContent != null) { output.write (m_aCachedContent); } else { final InputStream input = FileUtils.getInputStream (m_aDfosFile); StreamUtils.copyInputStreamToOutputStream (input, output); FileOperations.deleteFile (m_aDfosFile); m_aDfosFile = null; } output.close (); m_aCachedContent = null; } /** * Returns the file item headers. * * @return The file items headers. */ public IFileItemHeaders getHeaders () { return m_aHeaders; } /** * Sets the file item headers. * * @param pHeaders * The file items headers. */ public void setHeaders (final IFileItemHeaders pHeaders) { m_aHeaders = pHeaders; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy