Package yalp.data.parsing

Source Code of yalp.data.parsing.ApacheMultipartParser$AutoFileItem

package yalp.data.parsing;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;

import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.util.Closeable;
import org.apache.commons.fileupload.util.LimitedInputStream;
import org.apache.commons.fileupload.util.Streams;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;
import yalp.Logger;
import yalp.Yalp;
import yalp.data.FileUpload;
import yalp.data.MemoryUpload;
import yalp.data.Upload;
import yalp.exceptions.UnexpectedException;
import yalp.mvc.Http.Request;
import yalp.utils.HTTP;

/**
* From Apache commons fileupload.
* http://commons.apache.org/fileupload/
*/
public class ApacheMultipartParser extends DataParser {

    /*
     * Copyright 2001-2004 The Apache Software Foundation
     *
     * 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.
     *
     * <p> The default implementation of the
     * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
     *
     * <p> After retrieving an instance of this class from a {@link
     * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
     * {@link org.apache.commons.fileupload.DiskFileUpload
     * #parseRequest(javax.servlet.http.HttpServletRequest)}), 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 <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
     * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
     * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
     * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
     * @author Sean C. Sullivan
     *
     * @since FileUpload 1.1
     *
     * @version $Id: DiskFileItem.java,v 1.3 2005/07/26 03:05:02 rafaelsteil Exp $
     */
    public static class AutoFileItem implements FileItem {

        private static FileCleaningTracker fileTracker;

        static {
            fileTracker = new FileCleaningTracker();
        }

        // ----------------------------------------------------- Manifest constants
        /**
         * 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 = "ISO-8859-1";
        /**
         * Size of buffer to use when writing an item to disk.
         */
        private static final int WRITE_BUFFER_SIZE = 2048;

        // ----------------------------------------------------------- Data members
        /**
         * Counter used in unique identifier generation.
         */
        private static int counter = 0;
        /**
         * The name of the form field as provided by the browser.
         */
        private String fieldName;
        /**
         * The content type passed by the browser, or <code>null</code> if
         * not defined.
         */
        private String contentType;
        /**
         * Whether or not this item is a simple form field.
         */
        private boolean isFormField;
        /**
         * The original filename in the user's filesystem.
         */
        private String fileName;
        /**
         * The threshold above which uploads will be stored on disk.
         */
        private int sizeThreshold;
        /**
         * The directory in which uploaded files will be stored, if stored on disk.
         */
        private File repository;
        /**
         * Cached contents of the file.
         */
        private byte[] cachedContent;
        /**
         * Output stream for this item.
         */
        private DeferredFileOutputStream dfos;

        public AutoFileItem(FileItemStream stream) {
            this.fieldName = stream.getFieldName();
            this.contentType = stream.getContentType();
            this.isFormField = stream.isFormField();
            this.fileName = FilenameUtils.getName(stream.getName());
            this.sizeThreshold = Integer.parseInt(Yalp.configuration.getProperty("upload.threshold", "10240"));
            this.repository = null;
        }
        // ------------------------------- 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.
         * @throws IOException if an error occurs.
         */
        public InputStream getInputStream()
                throws IOException {
            if (!dfos.isInMemory()) {
                return new FileInputStream(dfos.getFile());
            }

            if (cachedContent == null) {
                cachedContent = dfos.getData();
            }
            return new ByteArrayInputStream(cachedContent);
        }

        /**
         * Returns the content type passed by the agent or <code>null</code> if
         * not defined.
         *
         * @return The content type passed by the agent or <code>null</code> if
         *         not defined.
         */
        public String getContentType() {
            return contentType;
        }

        /**
         * Returns the content charset passed by the agent or <code>null</code> if
         * not defined.
         *
         * @return The content charset passed by the agent or <code>null</code> if
         *         not defined.
         */
        public String getCharSet() {
            ParameterParser parser = new ParameterParser();
            parser.setLowerCaseNames(true);
            // Parameter parser can handle null input
            Map params = parser.parse(getContentType(), ';');
            return (String) params.get("charset");
        }

        /**
         * Returns the original filename in the client's filesystem.
         *
         * @return The original filename in the client's filesystem.
         */
        public String getName() {
            return fileName;
        }
        // ------------------------------------------------------- FileItem methods

        /**
         * Provides a hint as to whether or not the file contents will be read
         * from memory.
         *
         * @return <code>true</code> if the file contents will be read
         *         from memory; <code>false</code> otherwise.
         */
        public boolean isInMemory() {
            return (dfos.isInMemory());
        }

        /**
         * Returns the size of the file.
         *
         * @return The size of the file, in bytes.
         */
        public long getSize() {
            if (cachedContent != null) {
                return cachedContent.length;
            } else if (dfos.isInMemory()) {
                return dfos.getData().length;
            } else {
                return dfos.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.
         */
        public byte[] get() {
            if (dfos.isInMemory()) {
                if (cachedContent == null) {
                    cachedContent = dfos.getData();
                }
                return cachedContent;
            }

            byte[] fileData = new byte[(int) getSize()];
            FileInputStream fis = null;

            try {
                fis = new FileInputStream(dfos.getFile());
                fis.read(fileData);
            } catch (IOException e) {
                fileData = null;
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }

            return fileData;
        }

        /**
         * 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.
         */
        public String getString(final String charset)
                throws UnsupportedEncodingException {
            return new String(get(), charset);
        }

        /**
         * 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.
         * @todo Consider making this method throw UnsupportedEncodingException.
         */
        public String getString() {
            byte[] rawdata = get();
            String charset = getCharSet();
            if (charset == null) {
                charset = DEFAULT_CHARSET;
            }
            try {
                return new String(rawdata, charset);
            } catch (UnsupportedEncodingException e) {
                return new String(rawdata);
            }
        }

        /**
         * 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.
         * <p/>
         * 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.
         * <p/>
         * This method is only guaranteed to work <em>once</em>, 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 file The <code>File</code> into which the uploaded item should
         *             be stored.
         * @throws Exception if an error occurs.
         */
        public void write(File file) throws Exception {
            if (isInMemory()) {
                FileOutputStream fout = null;
                try {
                    fout = new FileOutputStream(file);
                    fout.write(get());
                } finally {
                    if (fout != null) {
                        fout.close();
                    }
                }
            } else {
                File outputFile = getStoreLocation();
                if (outputFile != null) {
                    /*
                     * The uploaded file is being stored on disk
                     * in a temporary location so move it to the
                     * desired file.
                     */
                    if (!outputFile.renameTo(file)) {
                        BufferedInputStream in = null;
                        BufferedOutputStream out = null;
                        try {
                            in = new BufferedInputStream(
                                    new FileInputStream(outputFile));
                            out = new BufferedOutputStream(
                                    new FileOutputStream(file));
                            byte[] bytes = new byte[WRITE_BUFFER_SIZE];
                            int s = 0;
                            while ((s = in.read(bytes)) != -1) {
                                out.write(bytes, 0, s);
                            }
                        } finally {
                            if (in != null) {
                                try {
                                    in.close();
                                } catch (IOException e) {
                                    // ignore
                                }
                            }
                            if (out != null) {
                                try {
                                    out.close();
                                } catch (IOException e) {
                                    // ignore
                                }
                            }
                        }
                    }
                } else {
                    /*
                     * 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 <code>FileItem</code> 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() {
            cachedContent = null;
            File outputFile = getStoreLocation();
            if (outputFile != null && outputFile.exists()) {
                outputFile.delete();
            }
        }

        /**
         * 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 fieldName;
        }

        /**
         * Sets the field name used to reference this file item.
         *
         * @param fieldName The name of the form field.
         * @see #getFieldName()
         */
        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        /**
         * Determines whether or not a <code>FileItem</code> instance represents
         * a simple form field.
         *
         * @return <code>true</code> if the instance represents a simple form
         *         field; <code>false</code> if it represents an uploaded file.
         * @see #setFormField(boolean)
         */
        public boolean isFormField() {
            return isFormField;
        }

        /**
         * Specifies whether or not a <code>FileItem</code> instance represents
         * a simple form field.
         *
         * @param state <code>true</code> if the instance represents a simple form
         *              field; <code>false</code> if it represents an uploaded file.
         * @see #isFormField()
         */
        public void setFormField(boolean state) {
            isFormField = 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 contensts of the file.
         * @throws IOException if an error occurs.
         */
        public OutputStream getOutputStream() throws IOException {
            if (dfos == null) {
                File outputFile = null;
                if (sizeThreshold != Integer.MAX_VALUE) {
                    outputFile = getTempFile();
                }
                dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
            }
            return dfos;
        }
        // --------------------------------------------------------- Public methods

        /**
         * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
         * data's temporary location on the disk. Note that for
         * <code>FileItem</code>s that have their data stored in memory,
         * this method will return <code>null</code>. 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 <code>null</code> if the data is stored in
         *         memory.
         */
        public File getStoreLocation() {
            return dfos.getFile();
        }
        // ------------------------------------------------------ Protected methods

        /**
         * Removes the file contents from the temporary storage.
         */
        protected void finalize() {
            File outputFile = dfos.getFile();

            if (outputFile != null && outputFile.exists()) {
                outputFile.delete();
            }
        }

        /**
         * 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 <code>FileItem</code> 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.
         */
        protected File getTempFile() {
            File tempDir = repository;
            if (tempDir == null) {
                tempDir = Yalp.tmpDir;
            }

            String fileName = "upload_" + getUniqueId() + ".tmp";

            File f = new File(tempDir, fileName);
            fileTracker.track(f, this);
            return f;
        }
        // -------------------------------------------------------- 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.
         */
        private static String getUniqueId() {
            int current;
            synchronized (DiskFileItem.class) {
                current = counter++;
            }
            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 < 100000000) {
                id = ("00000000" + id).substring(id.length());
            }
            return id;
        }

        public String toString() {
            return "name=" + this.getName() + ", StoreLocation=" + String.valueOf(this.getStoreLocation()) + ", size=" + this.getSize() + "bytes, " + "isFormField=" + isFormField() + ", FieldName=" + this.getFieldName();
        }
    }

    public Map<String, String[]> parse(InputStream body) {
        Map<String, String[]> result = new HashMap<String, String[]>();
        try {
            FileItemIteratorImpl iter = new FileItemIteratorImpl(body, Request.current().headers.get("content-type").value(), Request.current().encoding);
            while (iter.hasNext()) {
                FileItemStream item = iter.next();
                FileItem fileItem = new AutoFileItem(item);
                try {
                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
                } catch (FileUploadIOException e) {
                    throw (FileUploadException) e.getCause();
                } catch (IOException e) {
                    throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage(), e);
                }
                if (fileItem.isFormField()) {
                    // must resolve encoding
                    String _encoding = Request.current().encoding; // this is our default
                    String _contentType = fileItem.getContentType();
                    if (_contentType != null) {
                        HTTP.ContentTypeWithEncoding contentTypeEncoding = HTTP.parseContentType(_contentType);
                        if (contentTypeEncoding.encoding != null) {
                            _encoding = contentTypeEncoding.encoding;
                        }
                    }

                    putMapEntry(result, fileItem.getFieldName(), fileItem.getString(_encoding));
                } else {
                    @SuppressWarnings("unchecked") List<Upload> uploads = (List<Upload>) Request.current().args.get("__UPLOADS");
                    if (uploads == null) {
                        uploads = new ArrayList<Upload>();
                        Request.current().args.put("__UPLOADS", uploads);
                    }
                    try {
                        uploads.add(new FileUpload(fileItem));
                    } catch (Exception e) {
                        // GAE does not support it, we try in memory
                        uploads.add(new MemoryUpload(fileItem));
                    }
                    putMapEntry(result, fileItem.getFieldName(), fileItem.getFieldName());
                }
            }
        } catch (FileUploadIOException e) {
            Logger.debug(e, "error");
            throw new IllegalStateException("Error when handling upload", e);
        } catch (IOException e) {
            Logger.debug(e, "error");
            throw new IllegalStateException("Error when handling upload", e);
        } catch (FileUploadException e) {
            Logger.debug(e, "error");
            throw new IllegalStateException("Error when handling upload", e);
        } catch (Exception e) {
            Logger.debug(e, "error");
            throw new UnexpectedException(e);
        }
        return result;
    }    // ---------------------------------------------------------- Class methods
    // ----------------------------------------------------- Manifest constants
    /**
     * HTTP content type header name.
     */
    private static final String CONTENT_TYPE = "Content-type";
    /**
     * HTTP content disposition header name.
     */
    private static final String CONTENT_DISPOSITION = "Content-disposition";
    /**
     * Content-disposition value for form data.
     */
    private static final String FORM_DATA = "form-data";
    /**
     * Content-disposition value for file attachment.
     */
    private static final String ATTACHMENT = "attachment";
    /**
     * Part of HTTP content type header.
     */
    private static final String MULTIPART = "multipart/";
    /**
     * HTTP content type header for multipart forms.
     */
    private static final String MULTIPART_FORM_DATA = "multipart/form-data";
    /**
     * HTTP content type header for multiple uploads.
     */
    private static final String MULTIPART_MIXED = "multipart/mixed";
    // ----------------------------------------------------------- Data members
    /**
     * The maximum size permitted for the complete request, as opposed to
     * {@link #fileSizeMax}. A value of -1 indicates no maximum.
     */
    private long sizeMax = -1;
    /**
     * The maximum size permitted for a single uploaded file, as opposed to
     * {@link #sizeMax}. A value of -1 indicates no maximum.
     */
    private long fileSizeMax = -1;

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

    /**
     * Retrieves the boundary from the <code>Content-type</code> header.
     *
     * @param contentType The value of the content type header from which to extract the
     *                    boundary value.
     * @return The boundary, as a byte array.
     */
    private byte[] getBoundary(String contentType) {

        ParameterParser parser = new ParameterParser();
        parser.setLowerCaseNames(true);
        // Parameter parser can handle null input
        Map params = parser.parse(contentType, ';');
        String boundaryStr = (String) params.get("boundary");

        if (boundaryStr == null) {
            return null;
        }
        byte[] boundary;
        try {
            boundary = boundaryStr.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            boundary = boundaryStr.getBytes();
        }
        return boundary;
    }

    /**
     * Retrieves the file name from 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>.
     */
    private String getFileName(Map /* String, String */ headers) {
        String fileName = null;
        String cd = getHeader(headers, CONTENT_DISPOSITION);
        if (cd != null) {
            String cdl = cd.toLowerCase();
            if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
                ParameterParser parser = new ParameterParser();
                parser.setLowerCaseNames(true);
                // Parameter parser can handle null input
                Map params = parser.parse(cd, ';');
                if (params.containsKey("filename")) {
                    fileName = (String) params.get("filename");
                    if (fileName != null) {
                        fileName = fileName.trim();
                        // IE7 returning fullpath name (#300920)
                        if (fileName.indexOf('\\') != -1) {
                            fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
                        }

                    } else {
                        // Even if there is no value, the parameter is present,
                        // so we return an empty file name rather than no file
                        // name.
                        fileName = "";
                    }
                }
            }
        }
        return fileName;
    }

    /**
     * Retrieves the field name from 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>.
     */
    private String getFieldName(Map /* String, String */ headers) {
        String fieldName = null;
        String cd = getHeader(headers, CONTENT_DISPOSITION);
        if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {

            ParameterParser parser = new ParameterParser();
            parser.setLowerCaseNames(true);
            // Parameter parser can handle null input
            Map params = parser.parse(cd, ';');
            fieldName = (String) params.get("name");
            if (fieldName != null) {
                fieldName = fieldName.trim();
            }
        }
        return fieldName;
    }

    /**
     * <p/>
     * Parses the <code>header-part</code> and returns as key/value pairs.
     * <p/>
     * <p/>
     * If there are multiple headers of the same names, the name will map to 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.
     */
    private Map /* String, String */ parseHeaders(String headerPart) {
        final int len = headerPart.length();
        Map<String, String> headers = new HashMap<String, String>();
        int start = 0;
        for (; ; ) {
            int end = parseEndOfLine(headerPart, start);
            if (start == end) {
                break;
            }
            String header = headerPart.substring(start, end);
            start = end + 2;
            while (start < len) {
                int nonWs = start;
                while (nonWs < len) {
                    char c = headerPart.charAt(nonWs);
                    if (c != ' ' && c != '\t') {
                        break;
                    }
                    ++nonWs;
                }
                if (nonWs == start) {
                    break;
                }
                // Continuation line found
                end = parseEndOfLine(headerPart, nonWs);
                header += " " + headerPart.substring(nonWs, end);
                start = end + 2;
            }
            parseHeaderLine(headers, header);
        }
        return headers;
    }

    /**
     * Skips bytes until the end of the current line.
     *
     * @param headerPart The headers, which are being parsed.
     * @param end        Index of the last byte, which has yet been processed.
     * @return Index of the \r\n sequence, which indicates end of line.
     */
    private int parseEndOfLine(String headerPart, int end) {
        int index = end;
        for (; ; ) {
            int offset = headerPart.indexOf('\r', index);
            if (offset == -1 || offset + 1 >= headerPart.length()) {
                throw new IllegalStateException("Expected headers to be terminated by an empty line.");
            }
            if (headerPart.charAt(offset + 1) == '\n') {
                return offset;
            }
            index = offset + 1;
        }
    }

    /**
     * Reads the next header line.
     *
     * @param headers String with all headers.
     * @param header  Map where to store the current header.
     */
    private void parseHeaderLine(Map<String, String> headers, String header) {
        final int colonOffset = header.indexOf(':');
        if (colonOffset == -1) {
            // This header line is malformed, skip it.
            return;
        }
        String headerName = header.substring(0, colonOffset).trim().toLowerCase();
        String 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);
        }
    }

    /**
     * Returns the header with the specified name from 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 return.
     * @return The value of specified header, or a comma-separated list if there
     *         were multiple headers of that name.
     */
    private final String getHeader(Map /* String, String */ headers, String name) {
        return (String) headers.get(name.toLowerCase());
    }

    /**
     * The iterator, which is returned by
     * {@link FileUploadBase#getItemIterator(RequestContext)}.
     */
    private class FileItemIteratorImpl implements FileItemIterator {

        /**
         * Default implementation of {@link FileItemStream}.
         */
        private class FileItemStreamImpl implements FileItemStream {

            /**
             * The file items content type.
             */
            private final String contentType;
            /**
             * The file items field name.
             */
            private final String fieldName;
            /**
             * The file items file name.
             */
            private final String name;
            /**
             * Whether the file item is a form field.
             */
            private final boolean formField;
            /**
             * The file items input stream.
             */
            private final InputStream stream;
            /**
             * Whether the file item was already opened.
             */
            private boolean opened;

            private FileItemHeaders fileItemHeaders;

            /**
             * CReates a new instance.
             *
             * @param pName        The items file name, or null.
             * @param pFieldName   The items field name.
             * @param pContentType The items content type, or null.
             * @param pFormField   Whether the item is a form field.
             */
            FileItemStreamImpl(String pName, String pFieldName, String pContentType, boolean pFormField) {
                name = pName;
                fieldName = pFieldName;
                contentType = pContentType;
                formField = pFormField;
                InputStream istream = multi.newInputStream();
                if (fileSizeMax != -1) {
                    istream = new LimitedInputStream(istream, fileSizeMax) {

                        protected void raiseError(long pSizeMax, long pCount) throws IOException {
                            FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + pSizeMax + " characters.", pCount, pSizeMax);
                            throw new FileUploadIOException(e);
                        }
                    };
                }
                stream = istream;
            }

            public FileItemHeaders getHeaders() {
                return fileItemHeaders;
            }

            public void setHeaders(FileItemHeaders fileItemHeaders) {
                this.fileItemHeaders = fileItemHeaders;
            }


            /**
             * Returns the items content type, or null.
             *
             * @return Content type, if known, or null.
             */
            public String getContentType() {
                return contentType;
            }

            /**
             * Returns the items field name.
             *
             * @return Field name.
             */
            public String getFieldName() {
                return fieldName;
            }

            /**
             * Returns the items file name.
             *
             * @return File name, if known, or null.
             */
            public String getName() {
                return name;
            }

            /**
             * Returns, whether this is a form field.
             *
             * @return True, if the item is a form field, otherwise false.
             */
            public boolean isFormField() {
                return formField;
            }

            /**
             * Returns an input stream, which may be used to read the items
             * contents.
             *
             * @return Opened input stream.
             * @throws IOException An I/O error occurred.
             */
            public InputStream openStream() throws IOException {
                if (opened) {
                    throw new IllegalStateException("The stream was already opened.");
                }
                if (((Closeable) stream).isClosed()) {
                    throw new FileItemStream.ItemSkippedException();
                }
                return stream;
            }

            /**
             * Closes the file item.
             *
             * @throws IOException An I/O error occurred.
             */
            void close() throws IOException {
                stream.close();
            }
        }

        /**
         * The multi part stream to process.
         */
        private final MultipartStream multi;
        /**
         * The boundary, which separates the various parts.
         */
        private final byte[] boundary;
        /**
         * The item, which we currently process.
         */
        private FileItemStreamImpl currentItem;
        /**
         * The current items field name.
         */
        private String currentFieldName;
        /**
         * Whether we are currently skipping the preamble.
         */
        private boolean skipPreamble;
        /**
         * Whether the current item may still be read.
         */
        private boolean itemValid;
        /**
         * Whether we have seen the end of the file.
         */
        private boolean eof;

        /**
         * Creates a new instance.
         *
         * @param ctx The request context.
         * @throws FileUploadException An error occurred while parsing the request.
         * @throws IOException         An I/O error occurred.
         */
        FileItemIteratorImpl(InputStream input, String contentType, String charEncoding) throws FileUploadException, IOException {

            if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {
                throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType);
            }

            if (sizeMax >= 0) {
                // TODO check size

                input = new LimitedInputStream(input, sizeMax) {

                    protected void raiseError(long pSizeMax, long pCount) throws IOException {
                        FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
                        throw new FileUploadIOException(ex);
                    }
                };

            }

            boundary = getBoundary(contentType);
            if (boundary == null) {
                throw new FileUploadException("the request was rejected because " + "no multipart boundary was found");
            }

            multi = new MultipartStream(input, boundary, null);
            multi.setHeaderEncoding(charEncoding);

            skipPreamble = true;
            findNextItem();
        }

        /**
         * Called for finding the nex item, if any.
         *
         * @return True, if an next item was found, otherwise false.
         * @throws IOException An I/O error occurred.
         */
        private boolean findNextItem() throws IOException {
            if (eof) {
                return false;
            }
            if (currentItem != null) {
                currentItem.close();
                currentItem = null;
            }
            for (; ; ) {
                boolean nextPart;
                if (skipPreamble) {
                    nextPart = multi.skipPreamble();
                } else {
                    nextPart = multi.readBoundary();
                }
                if (!nextPart) {
                    if (currentFieldName == null) {
                        // Outer multipart terminated -> No more data
                        eof = true;
                        return false;
                    }
                    // Inner multipart terminated -> Return to parsing the outer
                    multi.setBoundary(boundary);
                    currentFieldName = null;
                    continue;
                }
                Map headers = parseHeaders(multi.readHeaders());
                if (currentFieldName == null) {
                    // We're parsing the outer multipart
                    String fieldName = getFieldName(headers);
                    if (fieldName != null) {
                        String subContentType = getHeader(headers, CONTENT_TYPE);
                        if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {
                            currentFieldName = fieldName;
                            // Multiple files associated with this field name
                            byte[] subBoundary = getBoundary(subContentType);
                            multi.setBoundary(subBoundary);
                            skipPreamble = true;
                            continue;
                        }
                        String fileName = getFileName(headers);
                        currentItem = new FileItemStreamImpl(fileName, fieldName, getHeader(headers, CONTENT_TYPE), fileName == null);

                        itemValid = true;
                        return true;
                    }
                } else {
                    String fileName = getFileName(headers);
                    if (fileName != null) {
                        currentItem = new FileItemStreamImpl(fileName, currentFieldName, getHeader(headers, CONTENT_TYPE), false);
                        itemValid = true;
                        return true;
                    }
                }
                multi.discardBodyData();
            }
        }

        /**
         * Returns, whether another instance of {@link FileItemStream} is
         * available.
         *
         * @return True, if one or more additional file items are available,
         *         otherwise false.
         * @throws FileUploadException Parsing or processing the file item failed.
         * @throws IOException         Reading the file item failed.
         */
        public boolean hasNext() throws FileUploadException, IOException {
            if (eof) {
                return false;
            }
            if (itemValid) {
                return true;
            }
            return findNextItem();
        }

        /**
         * Returns the next available {@link FileItemStream}.
         *
         * @return FileItemStream instance, which provides access to the next
         *         file item.
         * @throws java.util.NoSuchElementException
         *                             No more items are available. Use {@link #hasNext()} to
         *                             prevent this exception.
         * @throws FileUploadException Parsing or processing the file item failed.
         * @throws IOException         Reading the file item failed.
         */
        public FileItemStream next() throws FileUploadException, IOException {
            if (eof || (!itemValid && !hasNext())) {
                throw new NoSuchElementException();
            }
            itemValid = false;
            return currentItem;
        }
    }

    /**
     * This exception is thrown for hiding an inner {@link FileUploadException}
     * in an {@link IOException}.
     */
    private static class FileUploadIOException extends IOException {

        /**
         * The exceptions UID, for serializing an instance.
         */
        private static final long serialVersionUID = -7047616958165584154L;
        /**
         * The exceptions cause; we overwrite the parent classes field, which is
         * available since Java 1.4 only.
         */
        private final FileUploadException cause;

        /**
         * Creates a <code>FileUploadIOException</code> with the given cause.
         *
         * @param pCause The exceptions cause, if any, or null.
         */
        public FileUploadIOException(FileUploadException pCause) {
            // We're not doing super(pCause) cause of 1.3 compatibility.
            cause = pCause;
        }

        /**
         * Returns the exceptions cause.
         *
         * @return The exceptions cause, if any, or null.
         */
        public Throwable getCause() {
            return cause;
        }
    }

    /**
     * Thrown to indicate that the request is not a multipart request.
     */
    private static class InvalidContentTypeException extends FileUploadException {

        /**
         * The exceptions UID, for serializing an instance.
         */
        private static final long serialVersionUID = -9073026332015646668L;

        /**
         * Constructs an <code>InvalidContentTypeException</code> with the
         * specified detail message.
         *
         * @param message The detail message.
         */
        public InvalidContentTypeException(String message) {
            super(message);
        }
    }

    /**
     * Thrown to indicate an IOException.
     */
    private static class IOFileUploadException extends FileUploadException {

        /**
         * The exceptions UID, for serializing an instance.
         */
        private static final long serialVersionUID = 1749796615868477269L;
        /**
         * The exceptions cause; we overwrite the parent classes field, which is
         * available since Java 1.4 only.
         */
        private final IOException cause;

        /**
         * Creates a new instance with the given cause.
         *
         * @param pMsg       The detail message.
         * @param pException The exceptions cause.
         */
        public IOFileUploadException(String pMsg, IOException pException) {
            super(pMsg);
            cause = pException;
        }

        /**
         * Returns the exceptions cause.
         *
         * @return The exceptions cause, if any, or null.
         */
        public Throwable getCause() {
            return cause;
        }
    }

    /**
     * This exception is thrown, if a requests permitted size is exceeded.
     */
    protected abstract static class SizeException extends FileUploadException {

        /**
         * The actual size of the request.
         */
        private final long actual;
        /**
         * The maximum permitted size of the request.
         */
        private final long permitted;

        /**
         * Creates a new instance.
         *
         * @param message   The detail message.
         * @param actual    The actual number of bytes in the request.
         * @param permitted The requests size limit, in bytes.
         */
        protected SizeException(String message, long actual, long permitted) {
            super(message);
            this.actual = actual;
            this.permitted = permitted;
        }

        /**
         * Retrieves the actual size of the request.
         *
         * @return The actual size of the request.
         */
        public long getActualSize() {
            return actual;
        }

        /**
         * Retrieves the permitted size of the request.
         *
         * @return The permitted size of the request.
         */
        public long getPermittedSize() {
            return permitted;
        }
    }

    /**
     * Thrown to indicate that the request size exceeds the configured maximum.
     */
    private static class SizeLimitExceededException extends SizeException {

        /**
         * The exceptions UID, for serializing an instance.
         */
        private static final long serialVersionUID = -2474893167098052828L;

        /**
         * Constructs a <code>SizeExceededException</code> with the specified
         * detail message, and actual and permitted sizes.
         *
         * @param message   The detail message.
         * @param actual    The actual request size.
         * @param permitted The maximum permitted request size.
         */
        public SizeLimitExceededException(String message, long actual, long permitted) {
            super(message, actual, permitted);
        }
    }

    /**
     * Thrown to indicate that A files size exceeds the configured maximum.
     */
    private static class FileSizeLimitExceededException extends SizeException {

        /**
         * The exceptions UID, for serializing an instance.
         */
        private static final long serialVersionUID = 8150776562029630058L;

        /**
         * Constructs a <code>SizeExceededException</code> with the specified
         * detail message, and actual and permitted sizes.
         *
         * @param message   The detail message.
         * @param actual    The actual request size.
         * @param permitted The maximum permitted request size.
         */
        public FileSizeLimitExceededException(String message, long actual, long permitted) {
            super(message, actual, permitted);
        }
    }
}
TOP

Related Classes of yalp.data.parsing.ApacheMultipartParser$AutoFileItem

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.