org.mule.transport.http.multipart.MultiPartInputStream.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.transport.http.multipart.MultiPartInputStream.java

Source

/*
 * $Id$
 * -------------------------------------------------------------------------------------
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.transport.http.multipart;
// ========================================================================

// Copyright (c) 2006-2010 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================

import org.mule.model.streaming.DeleteOnCloseFileInputStream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ServletException;

import org.apache.commons.io.input.AutoCloseInputStream;

/**
* MultipartInputStream
*
* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
*/
public class MultiPartInputStream {
    public static final MultipartConfiguration __DEFAULT_MULTIPART_CONFIG = new MultipartConfiguration(
            System.getProperty("java.io.tmpdir"));
    protected InputStream _in;
    protected MultipartConfiguration _config;
    protected String _contentType;
    protected MultiMap _map;
    protected Map<String, Part> _parts;
    protected File _tmpDir;

    public class MultiPart implements Part {
        protected String _name;
        protected String _filename;
        protected File _file;
        protected OutputStream _out;
        protected String _contentType;
        protected MultiMap<String> _headers;
        protected long _size = 0;

        public MultiPart(String name, String filename) throws IOException {
            _name = name;
            _filename = filename;
        }

        protected void setContentType(String contentType) {
            _contentType = contentType;
        }

        protected void open() throws FileNotFoundException, IOException {
            //We will either be writing to a file, if it has a filename on the content-disposition
            //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
            //will need to change to write to a file.
            if (_filename != null && _filename.trim().length() > 0) {
                createFile();
            } else {
                //Write to a buffer in memory until we discover we've exceed the
                //MultipartConfig fileSizeThreshold
                _out = new ByteArrayOutputStream();
            }
        }

        protected void close() throws IOException {
            _out.close();
        }

        protected void write(int b) throws IOException {
            if (MultiPartInputStream.this._config.getMaxFileSize() > 0
                    && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize())
                throw new IOException("Multipart Mime part " + _name + " exceeds max filesize");

            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0
                    && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file == null)
                createFile();
            _out.write(b);
            _size++;
        }

        protected void write(byte[] bytes, int offset, int length) throws IOException {
            if (MultiPartInputStream.this._config.getMaxFileSize() > 0
                    && _size + length > MultiPartInputStream.this._config.getMaxFileSize())
                throw new IOException("Multipart Mime part " + _name + " exceeds max filesize");

            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0
                    && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file == null)
                createFile();

            _out.write(bytes, offset, length);
            _size += length;
        }

        protected void createFile() throws IOException {
            _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
            FileOutputStream fos = new FileOutputStream(_file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);

            if (_size > 0 && _out != null) {
                //already written some bytes, so need to copy them into the file
                _out.flush();
                ((ByteArrayOutputStream) _out).writeTo(bos);
                _out.close();
            }
            _out = bos;
        }

        protected void setHeaders(MultiMap<String> headers) {
            _headers = headers;
        }

        /**
         * @see Part#getContentType()
         */
        public String getContentType() {
            return _contentType;
        }

        /**
         * @see Part#getHeader(java.lang.String)
         */
        public String getHeader(String name) {
            return (String) _headers.getValue(name, 0);
        }

        /**
         * @see Part#getHeaderNames()
         */
        public Collection<String> getHeaderNames() {
            return _headers.keySet();
        }

        /**
         * @see Part#getHeaders(java.lang.String)
         */
        public Collection<String> getHeaders(String name) {
            return _headers.getValues(name);
        }

        /**
         * @see Part#getInputStream()
         */
        public InputStream getInputStream() throws IOException {
            if (_file != null) {
                // Automatically close and delete the temp file when end of input has been reached (MULE-6732).
                return new BufferedInputStream(new AutoCloseInputStream(new DeleteOnCloseFileInputStream(_file)));
            } else {
                //part content is in a ByteArrayOutputStream
                return new ByteArrayInputStream(((ByteArrayOutputStream) _out).toByteArray());
            }
        }

        /**
         * @see Part#getName()
         */
        public String getName() {
            return _name;
        }

        /**
         * @see Part#getSize()
         */
        public long getSize() {
            return _size;
        }

        /**
         * @see Part#write(java.lang.String)
         */
        public void write(String fileName) throws IOException {
            if (_file == null) {
                //part data is only in the ByteArrayOutputStream and never been written to disk
                _file = new File(_tmpDir, fileName);
                BufferedOutputStream bos = null;
                try {
                    bos = new BufferedOutputStream(new FileOutputStream(_file));
                    ((ByteArrayOutputStream) _out).writeTo(bos);
                    bos.flush();
                } finally {
                    if (bos != null)
                        bos.close();
                }
            } else {
                //the part data is already written to a temporary file, just rename it
                _file.renameTo(new File(_tmpDir, fileName));
            }
        }

        /**
         * @see Part#delete()
         */
        public void delete() throws IOException {
            if (_file != null)
                _file.delete();
        }

        /**
         * Get the file, if any, the data has been written to.
         * @return
         */
        public File getFile() {
            return _file;
        }

        /**
         * Get the filename from the content-disposition.
         * @return null or the filename
         */
        public String getContentDispositionFilename() {
            return _filename;
        }
    }

    /**
     * @param in Request input stream
     * @param contentType Content-Type header
     * @param config MultipartConfiguration
     */
    public MultiPartInputStream(InputStream in, String contentType, MultipartConfiguration config) {
        _in = new BufferedInputStream(in);
        _contentType = contentType;
        _config = config;
        if (_config == null)
            _config = __DEFAULT_MULTIPART_CONFIG;
    }

    public Collection<Part> getParts() throws IOException {
        parse();
        return _parts.values();
    }

    public Part getPart(String name) throws IOException, ServletException {
        parse();
        return _parts.get(name);
    }

    public MultiMap getMap() throws IOException, ServletException {
        parse();
        return _map;
    }

    protected void parse() throws IOException {
        //have we already parsed the input?
        if (_parts != null)
            return;

        //initialize
        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfiguration._maxRequestSize
        _parts = new HashMap<String, Part>();

        //if its not a multipart request, don't parse it
        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
            return;

        //sort out the location to which to write the files
        String location = __DEFAULT_MULTIPART_CONFIG.getLocation();
        location = ("".equals(_config.getLocation()) ? location : _config.getLocation());

        _tmpDir = new File(location);
        if (!_tmpDir.exists())
            _tmpDir.mkdirs();

        String boundary = "--" + value(_contentType.substring(_contentType.indexOf("boundary=")));
        byte[] byteBoundary = (boundary + "--").getBytes("ISO-8859-1");

        // Get first boundary
        byte[] bytes = readLine(_in);
        String line = bytes == null ? null : new String(bytes, "UTF-8");
        if (line == null || !line.equals(boundary)) {
            throw new IOException("Missing initial multi part boundary");
        }

        // Read each part
        boolean lastPart = false;
        String contentDisposition = null;
        String contentType = null;
        outer: while (!lastPart) {
            MultiMap<String> headers = new MultiMap<String>();
            while (true) {
                bytes = readLine(_in);
                if (bytes == null)
                    break outer;

                // If blank line, end of part headers
                if (bytes.length == 0)
                    break;

                total += bytes.length;
                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
                    throw new IOException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");

                line = new String(bytes, "UTF-8");

                //get content-disposition and content-type
                int c = line.indexOf(':', 0);
                if (c > 0) {
                    String key = line.substring(0, c).trim().toLowerCase();
                    String value = line.substring(c + 1, line.length()).trim();
                    headers.put(key, value);
                    if (key.equalsIgnoreCase("content-disposition"))
                        contentDisposition = value;
                    if (key.equalsIgnoreCase("content-type"))
                        contentType = value;
                }
            }

            // Extract content-disposition
            boolean form_data = false;
            if (contentDisposition == null) {
                throw new IOException("Missing content-disposition");
            }

            StringTokenizer tok = new StringTokenizer(contentDisposition, ";");
            String name = null;
            String filename = null;
            while (tok.hasMoreTokens()) {
                String t = tok.nextToken().trim();
                String tl = t.toLowerCase();
                if (t.startsWith("form-data"))
                    form_data = true;
                else if (tl.startsWith("name="))
                    name = value(t);
                else if (tl.startsWith("filename="))
                    filename = value(t);
            }

            // Check disposition
            if (!form_data) {
                continue;
            }
            //It is valid for reset and submit buttons to have an empty name.
            //If no name is supplied, the browser skips sending the info for that field.
            //However, if you supply the empty string as the name, the browser sends the
            //field, with name as the empty string. So, only continue this loop if we
            //have not yet seen a name field.
            if (name == null) {
                continue;
            }

            //Have a new Part
            MultiPart part = new MultiPart(name, filename);
            part.setHeaders(headers);
            part.setContentType(contentType);
            _parts.put(name, part);

            part.open();

            try {
                int state = -2;
                int c;
                boolean cr = false;
                boolean lf = false;

                // loop for all lines`
                while (true) {
                    int b = 0;
                    while ((c = (state != -2) ? state : _in.read()) != -1) {
                        total++;
                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
                            throw new IOException(
                                    "Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");

                        state = -2;
                        // look for CR and/or LF
                        if (c == 13 || c == 10) {
                            if (c == 13)
                                state = _in.read();
                            break;
                        }
                        // look for boundary
                        if (b >= 0 && b < byteBoundary.length && c == byteBoundary[b])
                            b++;
                        else {
                            // this is not a boundary
                            if (cr)
                                part.write(13);

                            if (lf)
                                part.write(10);

                            cr = lf = false;
                            if (b > 0)
                                part.write(byteBoundary, 0, b);

                            b = -1;
                            part.write(c);
                        }
                    }
                    // check partial boundary
                    if ((b > 0 && b < byteBoundary.length - 2) || (b == byteBoundary.length - 1)) {
                        if (cr)
                            part.write(13);

                        if (lf)
                            part.write(10);

                        cr = lf = false;
                        part.write(byteBoundary, 0, b);
                        b = -1;
                    }
                    // boundary match
                    if (b > 0 || c == -1) {
                        if (b == byteBoundary.length)
                            lastPart = true;
                        if (state == 10)
                            state = -2;
                        break;
                    }
                    // handle CR LF
                    if (cr)
                        part.write(13);

                    if (lf)
                        part.write(10);

                    cr = (c == 13);
                    lf = (c == 10 || state == 10);
                    if (state == 10)
                        state = -2;
                }
            } finally {

                part.close();
            }
        }
    }

    /* ------------------------------------------------------------ */
    private String value(String nameEqualsValue) {
        String value = nameEqualsValue.substring(nameEqualsValue.indexOf('=') + 1).trim();
        int i = value.indexOf(';');
        if (i > 0)
            value = value.substring(0, i);
        if (value.startsWith("\"")) {
            value = value.substring(1, value.indexOf('"', 1));
        } else {
            i = value.indexOf(' ');
            if (i > 0)
                value = value.substring(0, i);
        }
        return value;
    }

    public static int CR = '\015';
    public static int LF = '\012';

    private byte[] readLine(InputStream in) throws IOException {
        byte[] buf = new byte[256];

        int i = 0;
        int loops = 0;
        int ch = 0;

        while (true) {
            ch = in.read();
            if (ch < 0)
                break;
            loops++;

            // skip a leading LF's
            if (loops == 1 && ch == LF)
                continue;

            if (ch == CR || ch == LF)
                break;

            if (i >= buf.length) {
                byte[] old_buf = buf;
                buf = new byte[old_buf.length + 256];
                System.arraycopy(old_buf, 0, buf, 0, old_buf.length);
            }
            buf[i++] = (byte) ch;
        }

        if (ch == -1 && i == 0)
            return null;

        // skip a trailing LF if it exists
        if (ch == CR && in.available() >= 1 && in.markSupported()) {
            in.mark(1);
            ch = in.read();
            if (ch != LF)
                in.reset();
        }

        byte[] old_buf = buf;
        buf = new byte[i];
        System.arraycopy(old_buf, 0, buf, 0, i);

        return buf;
    }

}