Package org.slim3.controller.upload

Source Code of org.slim3.controller.upload.FileUpload$FileItemIteratorImpl$FileItemStreamImpl

/*
* Copyright 2004-2010 the Seasar Foundation and the Others.
*
* 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 org.slim3.controller.upload;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.servlet.http.HttpServletRequest;

import org.slim3.controller.upload.MultipartStream.ItemInputStream;

/**
* High level API for processing file uploads.
*
* @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>
* @author Sean C. Sullivan
* @author higa
* @since 1.0.0
*
*/
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";

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

    /**
     * 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.
     */
    public 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";

    // ----------------------------------------------------------- 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;

    /**
     * The content encoding to use when reading part headers.
     */
    private String headerEncoding;

    /**
     * <p>
     * Utility method that determines whether the request contains multipart
     * content.
     * </p>
     *
     * <p>
     * <strong>NOTE:</strong>This method will be moved to the
     * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
     * Unfortunately, since this method is static, it is not possible to provide
     * its replacement until this method is removed.
     * </p>
     *
     * @param request
     *            the request.
     *
     * @return <code>true</code> if the request is multipart; <code>false</code>
     *         otherwise.
     */
    public static final boolean isMultipartContent(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType == null) {
            return false;
        }
        if (contentType.toLowerCase().startsWith(MULTIPART)) {
            return true;
        }
        return false;
    }

    /**
     * Returns the maximum allowed size of a complete request, as opposed to
     * {@link #getFileSizeMax()}.
     *
     * @return The maximum allowed size, in bytes. The default value of -1
     *         indicates, that there is no limit.
     *
     * @see #setSizeMax(long)
     *
     */
    public long getSizeMax() {
        return sizeMax;
    }

    /**
     * Sets the maximum allowed size of a complete request, as opposed to
     * {@link #setFileSizeMax(long)}.
     *
     * @param sizeMax
     *            The maximum allowed size, in bytes. The default value of -1
     *            indicates, that there is no limit.
     *
     * @see #getSizeMax()
     *
     */
    public void setSizeMax(long sizeMax) {
        this.sizeMax = sizeMax;
    }

    /**
     * Returns the maximum allowed size of a single uploaded file, as opposed to
     * {@link #getSizeMax()}.
     *
     * @see #setFileSizeMax(long)
     * @return Maximum size of a single uploaded file.
     */
    public long getFileSizeMax() {
        return fileSizeMax;
    }

    /**
     * Sets the maximum allowed size of a single uploaded file, as opposed to
     * {@link #getSizeMax()}.
     *
     * @see #getFileSizeMax()
     * @param fileSizeMax
     *            Maximum size of a single uploaded file.
     */
    public void setFileSizeMax(long fileSizeMax) {
        this.fileSizeMax = fileSizeMax;
    }

    /**
     * Retrieves the character encoding used when reading the headers of an
     * individual part. When not specified, or <code>null</code>, the request
     * encoding is used. If that is also not specified, or <code>null</code>,
     * the platform default encoding is used.
     *
     * @return The encoding used to read part headers.
     */
    public String getHeaderEncoding() {
        return headerEncoding;
    }

    /**
     * Specifies the character encoding to be used when reading the headers of
     * individual part. When not specified, or <code>null</code>, the request
     * encoding is used. If that is also not specified, or <code>null</code>,
     * the platform default encoding is used.
     *
     * @param encoding
     *            The encoding used to read part headers.
     */
    public void setHeaderEncoding(String encoding) {
        headerEncoding = encoding;
    }

    /**
     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
     * compliant <code>multipart/form-data</code> stream.
     *
     * @param request
     *            The request.
     *
     * @return An iterator to instances of <code>FileItemStream</code> parsed
     *         from the request, in the order that they were transmitted.
     *
     * @throws FileUploadException
     *             if there are problems reading/parsing the request or storing
     *             files.
     * @throws IOException
     *             An I/O error occurred. This may be a network error while
     *             communicating with the client or a problem while storing the
     *             uploaded content.
     */
    public FileItemIterator getItemIterator(HttpServletRequest request)
            throws FileUploadException, IOException {
        return new FileItemIteratorImpl(request);
    }

    /**
     * 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.
     */
    protected byte[] getBoundary(String contentType) {
        ParameterParser parser = new ParameterParser();
        parser.setLowerCaseNames(true);
        // Parameter parser can handle null input
        Map<String, String> params =
            parser.parse(contentType, new char[] { ';', ',' });
        String boundaryStr = 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
     *            The HTTP headers object.
     *
     * @return The file name for the current <code>encapsulation</code>.
     */
    protected String getFileName(FileItemHeaders headers) {
        return getFileName(headers.getHeader(CONTENT_DISPOSITION));
    }

    /**
     * Returns the given content-disposition headers file name.
     *
     * @param pContentDisposition
     *            The content-disposition headers value.
     * @return The file name
     */
    protected String getFileName(String pContentDisposition) {
        String fileName = null;
        if (pContentDisposition != null) {
            String cdl = pContentDisposition.toLowerCase();
            if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
                ParameterParser parser = new ParameterParser();
                parser.setLowerCaseNames(true);
                // Parameter parser can handle null input
                Map<String, String> params =
                    parser.parse(pContentDisposition, ';');
                if (params.containsKey("filename")) {
                    fileName = params.get("filename");
                    if (fileName != null) {
                        fileName = fileName.trim();
                    } 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>.
     */
    protected String getFieldName(FileItemHeaders headers) {
        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
    }

    /**
     * Returns the field name, which is given by the content-disposition header.
     *
     * @param pContentDisposition
     *            The content-dispositions header value.
     * @return The field jake
     */
    protected String getFieldName(String pContentDisposition) {
        String fieldName = null;
        if (pContentDisposition != null
            && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
            ParameterParser parser = new ParameterParser();
            parser.setLowerCaseNames(true);
            // Parameter parser can handle null input
            Map<String, String> params = parser.parse(pContentDisposition, ';');
            fieldName = 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>
     * 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.
     */
    protected FileItemHeaders getParsedHeaders(String headerPart) {
        final int len = headerPart.length();
        FileItemHeaders headers = new FileItemHeaders();
        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.
     */
    protected 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.
     */
    protected void parseHeaderLine(FileItemHeaders headers, String header) {
        int colonOffset = header.indexOf(':');
        if (colonOffset == -1) {
            return;
        }
        String headerName = header.substring(0, colonOffset).trim();
        String headerValue = header.substring(header.indexOf(':') + 1).trim();
        headers.addHeader(headerName, headerValue);
    }

    /**
     * The default implementation of {@link FileItemIterator}.
     */
    protected class FileItemIteratorImpl implements FileItemIterator {

        /**
         * Default implementation of {@link FileItemStream}.
         */
        protected 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;
            /**
             * The headers, if any.
             */
            private FileItemHeaders headers;

            /**
             * 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.
             * @param pContentLength
             *            The items content length, if known, or -1
             * @throws IOException
             *             Creating the file item failed.
             */
            FileItemStreamImpl(String pName, String pFieldName,
                    String pContentType, boolean pFormField, long pContentLength)
                    throws IOException {
                name = pName;
                fieldName = pFieldName;
                contentType = pContentType;
                formField = pFormField;
                final ItemInputStream itemStream = multi.newInputStream();
                InputStream istream = itemStream;
                if (fileSizeMax != -1) {
                    if (pContentLength != -1 && pContentLength > fileSizeMax) {
                        throw new SizeLimitExceededException("The field "
                            + fieldName
                            + " exceeds its maximum permitted "
                            + " size of "
                            + fileSizeMax
                            + " characters.", pContentLength, fileSizeMax);
                    }
                    istream = new LimitedInputStream(istream, fileSizeMax) {
                        @Override
                        protected void raiseError(long pSizeMax, long pCount)
                                throws IOException {
                            itemStream.close(true);
                            throw new SizeLimitExceededException("The field "
                                + fieldName
                                + " exceeds its maximum permitted "
                                + " size of "
                                + pSizeMax
                                + " bytes.", pCount, pSizeMax);
                        }
                    };
                }
                stream = istream;
            }

            /**
             * 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 getFileName() {
                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 has bean already opened.");
                }
                return stream;
            }

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

            /**
             * Returns the file item headers.
             *
             * @return The items header object
             */
            public FileItemHeaders getHeaders() {
                return headers;
            }

            /**
             * Sets the file item headers.
             *
             * @param pHeaders
             *            The items header object
             */
            public void setHeaders(FileItemHeaders pHeaders) {
                headers = pHeaders;
            }
        }

        /**
         * The multi part stream to process.
         */
        protected MultipartStream multi;

        /**
         * The boundary, which separates the various parts.
         */
        protected byte[] boundary;

        /**
         * The item, which we currently process.
         */
        protected FileItemStreamImpl currentItem;

        /**
         * The current items field name.
         */
        protected String currentFieldName;

        /**
         * Whether we are currently skipping the preamble.
         */
        protected boolean skipPreamble;

        /**
         * Whether the current item may still be read.
         */
        private boolean itemValid;

        /**
         * Whether we have seen the end of the file.
         */
        protected 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(HttpServletRequest request)
                throws FileUploadException, IOException {
            if (request == null) {
                throw new NullPointerException("The request parameter is null.");
            }

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

            InputStream input = request.getInputStream();
            if (input == null) {
                eof = true;
                return;
            }
            if (sizeMax >= 0) {
                int requestSize = request.getContentLength();
                if (requestSize == -1) {
                    input = new LimitedInputStream(input, sizeMax) {
                        @Override
                        protected void raiseError(long pSizeMax, long pCount)
                                throws IOException {
                            throw new SizeLimitExceededException(
                                "the request was rejected because"
                                    + " its size ("
                                    + pCount
                                    + ") exceeds the configured maximum"
                                    + " ("
                                    + pSizeMax
                                    + ")",
                                pCount,
                                pSizeMax);
                        }
                    };
                } else {
                    if (sizeMax >= 0 && requestSize > sizeMax) {
                        throw new SizeLimitExceededException(
                            "the request was rejected because its size ("
                                + requestSize
                                + ") exceeds the configured maximum ("
                                + sizeMax
                                + ").",
                            requestSize,
                            sizeMax);
                    }
                }
            }

            String charEncoding = headerEncoding;
            if (charEncoding == null) {
                charEncoding = request.getCharacterEncoding();
            }

            boundary = getBoundary(contentType);
            if (boundary == null) {
                throw new FileUploadException(
                    "The request was rejected because "
                        + "no multipart boundary was found.");
            }
            multi = new MultipartStream(input, boundary);
            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.
         */
        protected 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;
                }
                FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
                if (currentFieldName == null) {
                    // We're parsing the outer multipart
                    String fieldName = getFieldName(headers);
                    if (fieldName != null) {
                        String subContentType = headers.getHeader(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,
                                headers.getHeader(CONTENT_TYPE),
                                fileName == null,
                                getContentLength(headers));
                        itemValid = true;
                        return true;
                    }
                } else {
                    String fileName = getFileName(headers);
                    if (fileName != null) {
                        currentItem =
                            new FileItemStreamImpl(
                                fileName,
                                currentFieldName,
                                headers.getHeader(CONTENT_TYPE),
                                false,
                                getContentLength(headers));
                        itemValid = true;
                        return true;
                    }
                }
                multi.discardBodyData();
            }
        }

        /**
         * Returns the content length.
         *
         * @param pHeaders
         *            the headers
         * @return the content length
         */
        protected long getContentLength(FileItemHeaders pHeaders) {
            try {
                return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
            } catch (Exception e) {
                return -1;
            }
        }

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

        /**
         * Returns the next available {@link FileItemStream}.
         *
         * @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.
         * @return FileItemStream instance, which provides access to the next
         *         file item.
         */
        public FileItemStream next() throws FileUploadException, IOException {
            if (eof || (!itemValid && !hasNext())) {
                throw new NoSuchElementException();
            }
            itemValid = false;
            return currentItem;
        }
    }
}
TOP

Related Classes of org.slim3.controller.upload.FileUpload$FileItemIteratorImpl$FileItemStreamImpl

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.