org.ajax4jsf.request.MultipartRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.ajax4jsf.request.MultipartRequest.java

Source

/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.ajax4jsf.request;

import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.ajax4jsf.exception.FileUploadException;
import org.ajax4jsf.request.ByteSequenceMatcher.BytesHandler;
import org.ajax4jsf.webapp.BaseXMLFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.FileUploadConstants;
import org.richfaces.model.UploadItem;

/**
 * Request wrapper for supporting multipart requests, used for file uploading.
 *
 * @author Shane Bryzak
 */
public class MultipartRequest extends HttpServletRequestWrapper {
    private static final String PARAM_NAME = "name";

    private static final String PARAM_FILENAME = "filename";

    private static final String PARAM_CONTENT_TYPE = "Content-Type";

    private static final int BUFFER_SIZE = 2048;

    private static final int CHUNK_SIZE = 512;

    private static final int MAX_HEADER_SIZE = 32768;

    private static final Log logger = LogFactory.getLog(MultipartRequest.class);

    private final boolean createTempFiles;

    private final String uid;

    private String encoding = null;

    private long contentLength = 0;

    private long bytesRead = 0;

    // we shouldn't allow to stop until request reaches PhaseListener because of
    // portlets
    private volatile boolean canStop = false;

    private Map<String, Param> parameters = null;

    private Map<String, Object> percentMap = null;

    private Map<String, Integer> requestSizeMap = null;

    private Map<String, String> requestKeysMap = null;

    private String requestKey = null;

    private MultipartRequestRegistry requestRegistry;

    private final List<String> keys = new ArrayList<String>();

    private byte[] boundaryMarker;

    private ByteSequenceMatcher sequenceMatcher;

    private static final byte CR = 0x0d;

    private static final byte LF = 0x0a;

    private static final byte[] CR_LF = { CR, LF };

    private static final byte[] HYPHENS = { 0x2d, 0x2d }; // '--'

    private boolean shouldStop = false;

    private boolean canceled;

    public MultipartRequest(HttpServletRequest request, boolean createTempFiles, int maxRequestSize, String uid) {
        super(request);
        this.createTempFiles = createTempFiles;
        this.uid = uid;

        String contentLength = request.getHeader("Content-Length");
        this.contentLength = Long.parseLong(contentLength);
        if (contentLength != null && maxRequestSize > 0 && this.contentLength > maxRequestSize) {
            // TODO : we should make decision if can generate exception in this
            // place
            // throw new FileUploadException(
            // "Multipart request is larger than allowed size");
        }
    }

    private class ControlledProgressInputStream extends FilterInputStream {

        protected ControlledProgressInputStream(InputStream in) {
            super(in);
        }

        @Override
        public int read() throws IOException {
            int read = super.read();
            if (read >= 0) {
                bytesRead++;
                fillProgressInfo();
            }
            return read;
        }

        @Override
        public int read(byte[] b) throws IOException {
            int read = super.read(b);
            if (read > 0) {
                bytesRead += read;
                fillProgressInfo();
            }
            return read;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int read = super.read(b, off, len);
            if (read > 0) {
                bytesRead += read;
                fillProgressInfo();
            }
            return read;
        }
    }

    private String decodeFileName(String name) {
        String fileName = null;

        try {
            if (getRequest().getParameter("_richfaces_send_http_error") != null) {
                fileName = new String(name.getBytes(encoding), "UTF-8");
            } else {
                StringBuffer buffer = new StringBuffer();
                String[] codes = name.split(";");
                if (codes != null) {
                    for (String code : codes) {
                        if (code.startsWith("&")) {
                            String sCode = code.replaceAll("[&#]*", "");
                            Integer iCode = Integer.parseInt(sCode);
                            buffer.append(Character.toChars(iCode));
                        } else {
                            buffer.append(code);
                        }
                    }
                    fileName = buffer.toString();
                }
            }
        } catch (Exception e) {
            fileName = name;
        }

        return fileName;
    }

    public void cancel() {
        this.canceled = true;

        if (parameters != null) {
            Iterator<Param> it = parameters.values().iterator();
            while (it.hasNext()) {
                Param p = it.next();
                if (p instanceof FileParam) {
                    ((FileParam) p).deleteFile();
                }
            }
        }
    }

    private void readNext() throws IOException {
        Param p = readHeader();
        if (p != null) {
            try {
                readData(p);
            } finally {
                try {
                    p.complete();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    private final int zeroReadAttempts = 20; // 20 attempts to read not-readable
                                             // data

    private Param createParam(Map<String, String> headers) {
        Param param = null;
        String paramName = headers.get(PARAM_NAME);
        if (paramName != null) {
            if (headers.containsKey(PARAM_FILENAME)) {
                FileParam fp = new FileParam(paramName);
                this.keys.add(paramName);

                if (createTempFiles) {
                    fp.createTempFile();
                }
                fp.setContentType(headers.get(PARAM_CONTENT_TYPE));
                fp.setFilename(decodeFileName(headers.get(PARAM_FILENAME)));
                param = fp;
            } else {
                if (parameters.containsKey(paramName)) {
                    param = parameters.get(paramName);
                } else {
                    param = new ValueParam(paramName, encoding);
                }
            }

            if (!parameters.containsKey(paramName)) {
                parameters.put(paramName, param);
            }
        }

        return param;
    }

    private boolean initialized = false;

    private static final BytesHandler NOOP_HANDLER = new BytesHandler() {
        public void handle(byte[] bytes, int length) {
            // do nothing
        }
    };

    private class HeadersHandler implements BytesHandler {

        private final ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);

        public void handle(byte[] bytes, int length) throws IOException {
            if (length != 0) {
                if (baos.size() + length > MAX_HEADER_SIZE) {
                    throw new IOException("Header section is too big");
                }

                baos.write(bytes, 0, length);
            }
        }

        public boolean dataEquals(byte[] bytes) {
            return (baos.size() == bytes.length) && Arrays.equals(HYPHENS, baos.toByteArray());
        }

        public String asString() throws UnsupportedEncodingException {
            if (encoding != null) {
                return baos.toString(encoding);
            } else {
                return baos.toString();
            }
        }

        public void reset() {
            baos.reset();
        }

    }

    private HeadersHandler headersHandler = null;

    private Param readHeader() throws IOException {
        if (sequenceMatcher.isEOF()) {
            return null;
        }

        if (headersHandler == null) {
            headersHandler = new HeadersHandler();
        } else {
            headersHandler.reset();
        }

        sequenceMatcher.setBytesHandler(headersHandler);
        sequenceMatcher.findSequence(-1, CR_LF);

        if (sequenceMatcher.isMatchedAndNotEOF() && !headersHandler.dataEquals(HYPHENS)) {
            headersHandler.reset();

            sequenceMatcher.findSequence(-1, CR_LF, CR_LF);

            if (!sequenceMatcher.isMatchedAndNotEOF()) {
                throw new IOException("Request header cannot be read");
            }

            String headersString = headersHandler.asString();
            Map<String, String> headers = new HashMap<String, String>();
            String[] split = headersString.split("\r\n");
            for (String headerString : split) {
                parseParams(headerString, "; ", headers);
            }

            return createParam(headers);
        }

        return null;
    }

    private void readProlog() throws IOException {
        sequenceMatcher.setBytesHandler(NOOP_HANDLER);
        sequenceMatcher.findSequence(-1, HYPHENS, boundaryMarker);
        if (!sequenceMatcher.isMatchedAndNotEOF()) {
            throw new IOException("Request prolog cannot be read");
        }
    }

    private void readData(final Param param) throws IOException {
        sequenceMatcher.setBytesHandler(param);
        sequenceMatcher.findSequence(CHUNK_SIZE, CR_LF, HYPHENS, boundaryMarker);
        if (!this.sequenceMatcher.isMatchedAndNotEOF()) {
            throw new IOException("Request data cannot be read");
        }
    }

    private void initialize() throws IOException {
        if (!initialized) {
            initialized = true;

            this.boundaryMarker = getBoundaryMarker(super.getContentType());
            if (this.boundaryMarker == null) {
                throw new FileUploadException(
                        "The request was rejected because " + "no multipart boundary was found");
            }

            if (HYPHENS.length + boundaryMarker.length + CHUNK_SIZE + CR_LF.length > BUFFER_SIZE) {
                throw new FileUploadException("Boundary marker is too long");
            }

            this.encoding = getCharacterEncoding();

            this.parameters = new HashMap<String, Param>();

            InputStream input = new ControlledProgressInputStream(getInputStream());

            this.sequenceMatcher = new ByteSequenceMatcher(input, BUFFER_SIZE);

            readProlog();
        }
    }

    public void parseRequest() {
        canStop = true;

        setupProgressData();

        try {
            initialize();

            while (!sequenceMatcher.isEOF()) {
                readNext();
            }

        } catch (IOException e) {
            this.cancel();

            if (!this.shouldStop) {
                throw new FileUploadException("IO Error parsing multipart request", e);
            }
        } finally {
            canStop = false;
        }
    }

    public static MultipartRequest lookupRequest(FacesContext context, String uploadId) {
        Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
        @SuppressWarnings("unchecked")
        Map<String, String> requestKeys = (Map<String, String>) sessionMap
                .get(FileUploadConstants.REQUEST_KEYS_BEAN_NAME);
        if (requestKeys != null) {
            String requestKey = requestKeys.get(uploadId);
            if (requestKey != null) {
                MultipartRequestRegistry requestRegistry = MultipartRequestRegistry.getInstance(context);
                if (requestRegistry != null) {
                    MultipartRequest request = requestRegistry.getRequest(requestKey);
                    if (request != null) {
                        return request;
                    }
                }
            }
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    private void setupProgressData() {
        if (percentMap == null || requestSizeMap == null || requestKeysMap == null) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            if (facesContext != null) {
                ExternalContext externalContext = facesContext.getExternalContext();
                if (externalContext != null) {
                    Map<String, Object> sessionMap = externalContext.getSessionMap();
                    if (sessionMap != null) {
                        String uploadId = getUploadId();

                        synchronized (sessionMap) {
                            if (percentMap == null) {
                                percentMap = (Map<String, Object>) sessionMap
                                        .get(FileUploadConstants.PERCENT_BEAN_NAME);
                                if (percentMap == null) {
                                    percentMap = new ConcurrentHashMap<String, Object>();
                                    sessionMap.put(FileUploadConstants.PERCENT_BEAN_NAME, percentMap);
                                }
                            }

                            if (requestSizeMap == null) {
                                requestSizeMap = (Map<String, Integer>) sessionMap
                                        .get(FileUploadConstants.REQUEST_SIZE_BEAN_NAME);
                                if (requestSizeMap == null) {
                                    requestSizeMap = new ConcurrentHashMap<String, Integer>();
                                    sessionMap.put(FileUploadConstants.REQUEST_SIZE_BEAN_NAME, requestSizeMap);
                                }
                            }

                            if (requestKeysMap == null) {
                                requestKeysMap = (Map<String, String>) sessionMap
                                        .get(FileUploadConstants.REQUEST_KEYS_BEAN_NAME);
                                if (requestKeysMap == null) {
                                    requestKeysMap = new ConcurrentHashMap<String, String>();
                                    sessionMap.put(FileUploadConstants.REQUEST_KEYS_BEAN_NAME, requestKeysMap);
                                }

                            }
                        }

                        percentMap.put(uploadId, Double.valueOf(0));

                        requestSizeMap.put(uploadId, getSize());

                        requestRegistry = MultipartRequestRegistry.getInstance(facesContext);
                        requestKey = requestRegistry.registerRequest(this);
                        requestKeysMap.put(uploadId, requestKey);

                    }
                }
            }
        }
    }

    private void fillProgressInfo() {
        setupProgressData();

        if (percentMap != null) {
            Double percent = 100.0 * this.bytesRead / this.contentLength;
            percentMap.put(uid, percent);
            // this.percent = percent;
        }
    }

    private byte[] getBoundaryMarker(String contentType) {
        Map<String, String> params = parseParams(contentType, ";");
        String boundaryStr = params.get("boundary");

        if (boundaryStr == null) {
            return null;
        }

        try {
            return boundaryStr.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            return boundaryStr.getBytes();
        }
    }

    private static final Pattern PARAM_VALUE_PATTERN = Pattern.compile("^\\s*([^\\s=]+)\\s*[=:]\\s*(.+)\\s*$");

    private static final Pattern FILE_NAME_PATTERN = Pattern.compile(".*filename=\"(.*)\"");

    private Map<String, String> parseParams(String paramStr, String separator) {
        Map<String, String> paramMap = new HashMap<String, String>();
        parseParams(paramStr, separator, paramMap);
        return paramMap;
    }

    private void parseParams(String paramStr, String separator, Map<String, String> paramMap) {
        String[] parts = paramStr.split(separator);

        for (String part : parts) {
            Matcher m = PARAM_VALUE_PATTERN.matcher(part);
            if (m.matches()) {
                String key = m.group(1);
                String value = m.group(2);

                // Strip double quotes
                if (value.startsWith("\"") && value.endsWith("\""))
                    value = value.substring(1, value.length() - 1);
                if (!"filename".equals(key)) {
                    paramMap.put(key, value);
                } else {
                    paramMap.put(key, parseFileName(paramStr));
                }
            }
        }
    }

    private String parseFileName(String parseStr) {
        Matcher m = FILE_NAME_PATTERN.matcher(parseStr);
        if (m.matches()) {
            String name = m.group(1);
            if (name.startsWith("&")) {
                return decodeFileName(name);
            } else {
                return name;
            }
        }
        return null;
    }

    private Param getParam(String name) {
        Param param = null;
        if (parameters != null) {
            param = parameters.get(name);
        }

        if (param == null) {
            if (!canceled) {
                try {
                    initialize();

                    while (param == null && !sequenceMatcher.isEOF()) {
                        readNext();
                        param = parameters.get(name);
                    }
                } catch (IOException e) {
                    this.cancel();
                    throw new FileUploadException("IO Error parsing multipart request", e);
                }
            }
        }

        return param;
    }

    public Integer getSize() {
        return contentLength > Integer.MAX_VALUE ? -1 : (int) contentLength;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Enumeration getParameterNames() {
        if (parameters == null) {
            parseRequest();
        }

        return Collections.enumeration(parameters.keySet());
    }

    public byte[] getFileBytes(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getData() : null;
    }

    public InputStream getFileInputStream(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getInputStream() : null;
    }

    public String getFileContentType(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getContentType() : null;
    }

    public Object getFile(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getFile() : null;
    }

    public String getFileName(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getFilename() : null;
    }

    public int getFileSize(String name) {
        Param p = getParam(name);
        return (p != null && p instanceof FileParam) ? ((FileParam) p).getFileSize() : -1;
    }

    @Override
    public String getParameter(String name) {
        Param p = getParam(name);
        if (p != null && p instanceof ValueParam) {
            ValueParam vp = (ValueParam) p;
            if (vp.getValue() instanceof String) {
                return (String) vp.getValue();
            }
        } else if (p != null && p instanceof FileParam) {
            return "---BINARY DATA---";
        } else {
            return super.getParameter(name);
        }

        return null;
    }

    @Override
    public String[] getParameterValues(String name) {
        parseRequest();

        Param p = getParam(name);
        if (p != null && p instanceof ValueParam) {
            ValueParam vp = (ValueParam) p;
            if (vp.getValue() instanceof List<?>) {
                @SuppressWarnings("unchecked")
                List<String> vals = (List<String>) vp.getValue();
                String[] values = new String[vals.size()];
                vals.toArray(values);
                return values;
            } else {
                return new String[] { (String) vp.getValue() };
            }
        } else {
            return super.getParameterValues(name);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map getParameterMap() {
        if (parameters == null) {
            parseRequest();
        }

        Map<String, Object> params = new HashMap<String, Object>(super.getParameterMap());

        for (String name : parameters.keySet()) {
            Param p = parameters.get(name);
            if (p instanceof ValueParam) {
                ValueParam vp = (ValueParam) p;
                if (vp.getValue() instanceof String) {
                    params.put(name, vp.getValue());
                } else if (vp.getValue() instanceof List) {
                    params.put(name, getParameterValues(name));
                }
            }
        }

        return params;
    }

    public List<UploadItem> getUploadItems() {
        List<UploadItem> uploadItems = new ArrayList<UploadItem>();
        for (String k : keys) {
            uploadItems.add(new UploadItem(getFileName(k), getFileSize(k), getFileContentType(k), getFile(k)));
        }
        return uploadItems;
    }

    public boolean isFormUpload() {
        return "_richfaces_form_upload".equals(uid);
    }

    @Override
    public String getHeader(String name) {
        if (!"Accept".equals(name)) {
            return super.getHeader(name);
        } else {
            return BaseXMLFilter.TEXT_HTML;
        }
    }

    public void stop() {
        if (canStop) {
            shouldStop = true;
        }
    }

    public boolean isStopped() {
        return this.shouldStop;
    }

    public boolean isDone() {
        return !(this.shouldStop && (this.canceled || this.contentLength != this.bytesRead));
    }

    @Override
    public String getContentType() {
        return "application/x-www-form-urlencoded";
    }

    protected String getUploadId() {
        return uid;
    }

    public void clearRequestData() {
        String uploadId = getUploadId();

        if (percentMap != null) {
            percentMap.remove(uploadId);
        }

        if (requestSizeMap != null) {
            requestSizeMap.remove(uploadId);
        }

        if (requestKeysMap != null) {
            requestKeysMap.remove(uploadId);
        }

        if (requestRegistry != null) {
            requestRegistry.removeRequest(requestKey);
        }
    }
}