net.naijatek.myalumni.util.fileupload.FileUpload.java Source code

Java tutorial

Introduction

Here is the source code for net.naijatek.myalumni.util.fileupload.FileUpload.java

Source

/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package net.naijatek.myalumni.util.fileupload;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.MethodUtils;

/**
 * <p>High level API for processing file uploads.
 * 
 * <p>This class handles multiple files per single HTML widget, sent using
 * <code>multipar/mixed</code> encoding type, as specified by
 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@@link
 * #parseRequest(HttpServletRequest)} to_email acquire a list of {@@link
 * net.naijatek.naijatekcore.fileupload.FileItem}s associated with a given HTML
 * widget.
 * 
 * <p> Files will be stored in temporary disk storage or in memory,
 * depending on request size, and will be available as {@@link
 * net.naijatek.naijatekcore.fileupload.FileItem}s.
 * 
 * @author <a href="mailto:Rafal.Krzewski@@e-point.pl">Rafal Krzewski</a>
 * @author <a href="mailto:dlr@@collab.net">Daniel Rall</a>
 * @author <a href="mailto:jvanzyl@@apache.org">Jason van Zyl</a>
 * @author <a href="mailto:jmcnally@@collab.net">John McNally</a>
 * @author <a href="mailto:martinc@@apache.org">Martin Cooper</a>
 * 
 * @version $Id: FileUpload.java,v 1.2 2002/11/26 03:23:57 minhnn Exp $
 */
public class FileUpload {

    // ----------------------------------------------------- Manifest constants

    /**
     * HTTP content type header name.
     */
    public static final String CONTENT_TYPE = "Content-type";

    /**
     * HTTP content disposition header name.
     */
    public static final String CONTENT_DISPOSITION = "Content-disposition";

    /**
     * Content-disposition value for form data.
     */
    public static final String FORM_DATA = "form-data";

    /**
     * Content-disposition value for file attachment.
     */
    public static final String ATTACHMENT = "attachment";

    /**
     * Part of HTTP content type header.
     */
    private static final String MULTIPART = "multipart/";

    /**
     * HTTP content type header for multipart forms.
     */
    public static final String MULTIPART_FORM_DATA = "multipart/form-data";

    /**
     * HTTP content type header for multiple uploads.
     */
    public static final String MULTIPART_MIXED = "multipart/mixed";

    /**
     * The maximum length of a single header line that will be parsed
     * (1024 bytes).
     */
    public static final int MAX_HEADER_SIZE = 1024;

    // ----------------------------------------------------------- Data members

    /**
     * The maximum size permitted for an uploaded file.
     */
    private int sizeMax;

    /**
     * The threshold above which uploads will be stored on disk.
     */
    private int sizeThreshold;

    /**
     * The path to_email which uploaded files will be stored, if stored on disk.
     */
    private String repositoryPath;

    /**
     * The name of the class to_email use for <code>FileItem</code>s.
     */
    private String fileItemClassName = "net.naijatek.naijatekcore.fileupload.DefaultFileItem";

    /**
     * The cached method for obtaining a new <code>FileItem</code> instance.
     */
    private Method newInstanceMethod;

    // ----------------------------------------------------- Property accessors

    /**
     * Returns the maximum allowed upload size.
     *
    * @return The maximum allowed size, in bytes.
     */
    public int getSizeMax() {
        return sizeMax;
    }

    /**
     * Sets the maximum allowed upload size. If negative, there is no maximum.
     *
    * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
     */
    public void setSizeMax(final int sizeMax) {
        this.sizeMax = sizeMax;
    }

    /**
     * Returns the size threshold beyond which files are written directly to_email
     * disk. The default value is 1024 bytes.
     * 
     * @return The size threshold, in bytes.
     */
    public int getSizeThreshold() {
        return sizeThreshold;
    }

    /**
     * Sets the size threshold beyond which files are written directly to_email disk.
     * 
     * @param sizeThreshold The size threshold, in bytes.
     */
    public void setSizeThreshold(final int sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }

    /**
     * Returns the location used to_email temporarily store files that are larger
     * than the configured size threshold.
     * 
     * @return The path to_email the temporary file location.
     */
    public String getRepositoryPath() {
        return repositoryPath;
    }

    /**
     * Sets the location used to_email temporarily store files that are larger
     * than the configured size threshold.
     * 
     * @param repositoryPath The path to_email the temporary file location.
     */
    public void setRepositoryPath(final String repositoryPath) {
        this.repositoryPath = repositoryPath;
    }

    /**
     * Returns the fully qualified name of the class which will be used to_email
     * instantiate <code>FileItem</code> instances when a request is parsed.
     * 
     * @return The fully qualified name of the Java class.
     */
    public String getFileItemClassName() {
        return fileItemClassName;
    }

    /**
     * Sets the fully qualified name of the class which will be used to_email
     * instantiate <code>FileItem</code> instances when a request is parsed.
     * 
     * @param fileItemClassName The fully qualified name of the Java class.
     */
    public void setFileItemClassName(final String fileItemClassName) {
        this.fileItemClassName = fileItemClassName;
        this.newInstanceMethod = null;
    }

    // --------------------------------------------------------- Public methods

    /**
     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
     * compliant <code>multipart/form-data</code> stream. If files are stored
     * on disk, the path is given by <code>getRepositoryPath()</code>.
     * 
     * @param req The servlet request to_email be parsed.
     * 
     * @return A list of <code>FileItem</code> instances parsed from_email the
     * request, in the order that they were transmitted.
     * 
     * @exception FileUploadException if there are problems reading/parsing
     * the request or storing files.
     */
    public List /* FileItem */ parseRequest(final HttpServletRequest req) throws FileUploadException {
        return parseRequest(req, getSizeThreshold(), getSizeMax(), getRepositoryPath());
    }

    /**
     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
     * compliant <code>multipart/form-data</code> stream. If files are stored
     * on disk, the path is given by <code>getRepositoryPath()</code>.
     * 
     * @param req The servlet request to_email be parsed.
     * @param sizeThreshold The max size in bytes to_email be stored in memory.
     * @param sizeMax The maximum allowed upload size, in bytes.
     * @param path The location where the files should be stored.
     * 
     * @return A list of <code>FileItem</code> instances parsed from_email the
     * request, in the order that they were transmitted.
     * 
     * @exception FileUploadException if there are problems reading/parsing
     * the request or storing files.
     */
    public List /* FileItem */ parseRequest(final HttpServletRequest req, final int sizeThreshold,
            final int sizeMax, final String path) throws FileUploadException {
        ArrayList items = new ArrayList();
        String contentType = req.getHeader(CONTENT_TYPE);

        if (!contentType.startsWith(MULTIPART)) {
            throw new FileUploadException(
                    "the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream");
        }
        int requestSize = req.getContentLength();

        if (requestSize == -1) {
            throw new FileUploadException("the request was rejected because " + "it's size is unknown");
        }

        if (sizeMax >= 0 && requestSize > sizeMax) {
            throw new FileUploadException("the request was rejected because " + "it's size exceeds allowed range");
        }

        try {
            byte[] boundary = contentType.substring(contentType.indexOf("boundary=") + 9).getBytes();

            InputStream input = req.getInputStream();

            MultipartStream multi = new MultipartStream(input, boundary);
            boolean nextPart = multi.skipPreamble();
            while (nextPart) {
                Map headers = parseHeaders(multi.readHeaders());
                String fieldName = getFieldName(headers);
                if (fieldName != null) {
                    String subContentType = getHeader(headers, CONTENT_TYPE);
                    if (subContentType != null && subContentType.startsWith(MULTIPART_MIXED)) {
                        // Multiple files.
                        byte[] subBoundary = subContentType.substring(subContentType.indexOf("boundary=") + 9)
                                .getBytes();
                        multi.setBoundary(subBoundary);
                        boolean nextSubPart = multi.skipPreamble();
                        while (nextSubPart) {
                            headers = parseHeaders(multi.readHeaders());
                            if (getFileName(headers) != null) {
                                FileItem item = createItem(sizeThreshold, path, headers, requestSize);
                                OutputStream os = ((DefaultFileItem) item).getOutputStream();
                                try {
                                    multi.readBodyData(os);
                                } finally {
                                    os.close();
                                }
                                item.setFieldName(getFieldName(headers));
                                items.add(item);
                            } else {
                                // Ignore anything but files inside
                                // multipart/mixed.
                                multi.discardBodyData();
                            }
                            nextSubPart = multi.readBoundary();
                        }
                        multi.setBoundary(boundary);
                    } else {
                        if (getFileName(headers) != null) {
                            // A single file.
                            FileItem item = createItem(sizeThreshold, path, headers, requestSize);
                            OutputStream os = ((DefaultFileItem) item).getOutputStream();
                            try {
                                multi.readBodyData(os);
                            } finally {
                                os.close();
                            }
                            item.setFieldName(getFieldName(headers));
                            items.add(item);
                        } else {
                            // A form field.
                            FileItem item = createItem(sizeThreshold, path, headers, requestSize);
                            OutputStream os = ((DefaultFileItem) item).getOutputStream();
                            try {
                                multi.readBodyData(os);
                            } finally {
                                os.close();
                            }
                            item.setFieldName(getFieldName(headers));
                            item.setIsFormField(true);
                            items.add(item);
                        }
                    }
                } else {
                    // Skip this part.
                    multi.discardBodyData();
                }
                nextPart = multi.readBoundary();
            }
        } catch (IOException e) {
            throw new FileUploadException(
                    "Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage());
        }

        return items;
    }

    // ------------------------------------------------------ Protected methods

    /**
     * Retrieves the file name from_email the <code>Content-disposition</code>
     * header.
     * 
     * @param headers A <code>Map</code> containing the HTTP request headers.
     * 
     * @return The file name for the current <code>encapsulation</code>.
     */
    protected String getFileName(final Map /* String, String */ headers) {
        String fileName = null;
        String cd = getHeader(headers, CONTENT_DISPOSITION);
        if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) {
            int start = cd.indexOf("filename=\"");
            int end = cd.indexOf('"', start + 10);
            if (start != -1 && end != -1) {
                fileName = cd.substring(start + 10, end).trim();
            }
        }
        return fileName;
    }

    /**
     * Retrieves the field name from_email the <code>Content-disposition</code>
     * header.
     * 
     * @param headers A <code>Map</code> containing the HTTP request headers.
     * 
     * @return The field name for the current <code>encapsulation</code>.
     */
    protected String getFieldName(final Map /* String, String */ headers) {
        String fieldName = null;
        String cd = getHeader(headers, CONTENT_DISPOSITION);
        if (cd != null && cd.startsWith(FORM_DATA)) {
            int start = cd.indexOf("name=\"");
            int end = cd.indexOf('"', start + 6);
            if (start != -1 && end != -1) {
                fieldName = cd.substring(start + 6, end);
            }
        }
        return fieldName;
    }

    /**
     * Creates a new {@@link net.naijatek.naijatekcore.fileupload.FileItem} instance.
     * 
     * @param sizeThreshold The max size in bytes to_email be stored in memory.
     * @param path The path for the FileItem.
     * @param headers A <code>Map</code> containing the HTTP request
     * headers.
     * @param requestSize The total size of the request, in bytes.
     * 
     * @return A newly created <code>FileItem</code> instance.
     * 
     * @exception FileUploadException if an error occurs.
     */
    protected FileItem createItem(final int sizeThreshold, final String path,
            final Map /* String, String */ headers, final int requestSize) throws FileUploadException {
        Method newInstanceMethod = getNewInstanceMethod();
        Object[] args = new Object[] { path, getFileName(headers), getHeader(headers, CONTENT_TYPE),
                new Integer(requestSize), new Integer(sizeThreshold) };
        FileItem fileItem = null;

        try {
            fileItem = (FileItem) newInstanceMethod.invoke(null, args);
        } catch (Exception e) {
            throw new FileUploadException(e.toString());
        }

        return fileItem;
    }

    /**
     * <p> Returns the <code>Method</code> object to_email be used to_email obtain a new
     * <code>FileItem</code> instance.
     * 
     * <p> For performance reasons, we cache the method once it has been
     * looked up, since method lookup is one of the more expensive aspects
     * of reflection.
     * 
     * @return The <code>newInstance()</code> method to_email be invoked.
     * 
     * @exception FileUploadException if an error occurs.
     */
    protected Method getNewInstanceMethod() throws FileUploadException {
        // If the method is already cached, just return it.
        if (this.newInstanceMethod != null) {
            return this.newInstanceMethod;
        }

        // Load the FileUpload implementation class.
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class fileItemClass = null;

        if (classLoader == null) {
            classLoader = getClass().getClassLoader();
        }

        try {
            fileItemClass = classLoader.loadClass(fileItemClassName);
        } catch (Exception e) {
            throw new FileUploadException(e.toString());
        }

        if (fileItemClass == null) {
            throw new FileUploadException("Failed to load FileItem class: " + fileItemClassName);
        }

        // Find the newInstance() method.
        Class[] parameterTypes = new Class[] { String.class, String.class, String.class, Integer.TYPE,
                Integer.TYPE };
        Method newInstanceMethod = MethodUtils.getAccessibleMethod(fileItemClass, "newInstance", parameterTypes);

        if (newInstanceMethod == null) {
            throw new FileUploadException(
                    "Failed find newInstance() method in FileItem class: " + fileItemClassName);
        }

        // Cache the method so that all this only happens once.
        this.newInstanceMethod = newInstanceMethod;
        return newInstanceMethod;
    }

    /**
     * <p> Parses the <code>header-part</code> and returns as key/value
     * pairs.
     * 
     * <p> If there are multiple headers of the same names, the name
     * will map to_email a comma-separated list containing the values.
     * 
     * @param headerPart The <code>header-part</code> of the current
     * <code>encapsulation</code>.
     * 
     * @return A <code>Map</code> containing the parsed HTTP request headers.
     */
    protected Map /* String, String */ parseHeaders(final String headerPart) {
        Map headers = new HashMap();
        char buffer[] = new char[MAX_HEADER_SIZE];
        boolean done = false;
        int j = 0;
        int i;
        String header, headerName, headerValue;
        try {
            while (!done) {
                i = 0;
                // Copy a single line of characters into the buffer,
                // omitting trailing CRLF.
                while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n') {
                    buffer[i++] = headerPart.charAt(j++);
                }
                header = new String(buffer, 0, i - 2);
                if (header.equals("")) {
                    done = true;
                } else {
                    if (header.indexOf(':') == -1) {
                        // This header line is malformed, skip it.
                        continue;
                    }
                    headerName = header.substring(0, header.indexOf(':')).trim().toLowerCase();
                    headerValue = header.substring(header.indexOf(':') + 1).trim();
                    if (getHeader(headers, headerName) != null) {
                        // More that one heder of that name exists,
                        // append to the list.
                        headers.put(headerName, getHeader(headers, headerName) + ',' + headerValue);
                    } else {
                        headers.put(headerName, headerValue);
                    }
                }
            }
        } catch (IndexOutOfBoundsException e) {
            // Headers were malformed. continue with all that was
            // parsed.
        }
        return headers;
    }

    /**
     * Returns the header with the specified name from_email the supplied map. The
     * header lookup is case-insensitive.
     * 
     * @param headers A <code>Map</code> containing the HTTP request headers.
     * @param name The name of the header to_email return.
     * 
     * @return The value of specified header, or a comma-separated list if
     * there were multiple headers of that name.
     */
    protected final String getHeader(final Map /* String, String */ headers, final String name) {
        return (String) headers.get(name.toLowerCase());
    }

}