org.richfaces.request.MultipartRequestParser Maven / Gradle / Ivy
The newest version!
/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.request;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.richfaces.exception.FileUploadException;
import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
import org.richfaces.model.UploadedFile;
import org.richfaces.request.ByteSequenceMatcher.BytesHandler;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
/**
* @author Nick Belaevski
*
*/
public final class MultipartRequestParser {
static final String PARAM_NAME = "name";
static final String PARAM_FILENAME = "filename";
static final String PARAM_CONTENT_TYPE = "Content-Type";
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 static final int BUFFER_SIZE = 2048;
private static final int CHUNK_SIZE = 1024;
private static final int MAX_HEADER_SIZE = 32768;
private static final Logger LOGGER = RichfacesLogger.APPLICATION.getLogger();
private static final Pattern FILE_NAME_PATTERN = Pattern.compile(".*filename=\"(.*)\"");
private static final Pattern PARAM_VALUE_PATTERN = Pattern.compile("^\\s*([^\\s=]+)\\s*[=:]\\s*(.+)\\s*$");
private static final BytesHandler NOOP_HANDLER = new BytesHandler() {
public void handle(byte[] bytes, int length) {
// do nothing
}
};
private class HeadersHandler implements BytesHandler {
private 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 (request.getCharacterEncoding() != null) {
return baos.toString(request.getCharacterEncoding());
} else {
return baos.toString();
}
}
public void reset() {
baos.reset();
}
}
private HttpServletRequest request;
private boolean createTempFiles;
private String tempFilesDirectory;
private Multimap parametersMap = LinkedListMultimap.create();
private List uploadedFiles = Lists.newArrayList();
private byte[] boundaryMarker;
private ByteSequenceMatcher sequenceMatcher;
private HeadersHandler headersHandler;
private ProgressControl progressControl;
/**
* @param request
* @param createTempFiles
* @param tempFilesDirectory
* @param uploadId
*/
public MultipartRequestParser(HttpServletRequest request, boolean createTempFiles, String tempFilesDirectory,
ProgressControl progressControl) {
this.request = request;
this.createTempFiles = createTempFiles;
this.tempFilesDirectory = tempFilesDirectory;
this.progressControl = progressControl;
}
private void cancel() {
for (UploadedFile uploadedFile : uploadedFiles) {
try {
uploadedFile.delete();
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
}
}
public Multimap getParameters() {
return parametersMap;
}
public Iterable getUploadedFiles() {
return uploadedFiles;
}
public void parse() throws FileUploadException {
try {
initialize();
while (!sequenceMatcher.isEOF()) {
readNext();
}
} catch (IOException e) {
this.cancel();
throw new FileUploadException(MessageFormat.format("Exception parsing multipart request: {0}", e.getMessage()), e);
}
}
private void initialize() throws IOException, FileUploadException {
this.boundaryMarker = getBoundaryMarker(request.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.sequenceMatcher = new ByteSequenceMatcher(progressControl.wrapStream(request.getInputStream()), BUFFER_SIZE);
readProlog();
}
private String getFirstParameterValue(Multimap multimap, String key) {
Collection values = multimap.get(key);
if (values.isEmpty()) {
return null;
}
return Iterables.get(values, 0);
}
private byte[] getBoundaryMarker(String contentType) {
Multimap params = parseParams(contentType, ";");
String boundaryStr = getFirstParameterValue(params, "boundary");
if (boundaryStr == null) {
return null;
}
try {
return boundaryStr.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
return boundaryStr.getBytes();
}
}
private Multimap parseParams(String paramStr, String separator) {
Multimap paramMap = LinkedListMultimap.create();
parseParams(paramStr, separator, paramMap);
return paramMap;
}
private void parseParams(String paramStr, String separator, Multimap 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).toLowerCase(Locale.US);
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));
}
}
}
}
// TODO - URI decoder?
private String decodeFileName(String name) {
String fileName = null;
try {
StringBuilder builder = new StringBuilder();
String[] codes = name.split(";");
if (codes != null) {
for (String code : codes) {
if (code.startsWith("&")) {
String sCode = code.replaceAll("[]*", "");
Integer iCode = Integer.parseInt(sCode);
builder.append(Character.toChars(iCode));
} else {
builder.append(code);
}
}
fileName = builder.toString();
}
} catch (Exception e) {
fileName = name;
}
return fileName;
}
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 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(FileUploadParam uploadParam) throws IOException {
sequenceMatcher.setBytesHandler(uploadParam);
sequenceMatcher.findSequence(CHUNK_SIZE, CR_LF, HYPHENS, boundaryMarker);
sequenceMatcher.setBytesHandler(null);
if (!this.sequenceMatcher.isMatchedAndNotEOF()) {
throw new IOException("Request data cannot be read");
}
}
private void readNext() throws IOException {
Multimap headers = readHeaders();
FileUploadParam param = createParam(headers);
if (param == null) {
return;
}
param.create();
try {
readData(param);
} finally {
param.complete();
}
if (param.isFileParam()) {
uploadedFiles.add(new UploadedFile25(param.getName(), param.getResource(), headers));
} else {
parametersMap.put(param.getName(), param.getValue());
}
}
private FileUploadParam createParam(Multimap headers) {
if (headers == null) {
return null;
}
String parameterName = getFirstParameterValue(headers, PARAM_NAME);
if (Strings.isNullOrEmpty(parameterName)) {
return null;
}
boolean isFile = !Strings.isNullOrEmpty(getFirstParameterValue(headers, PARAM_FILENAME));
FileUploadParam param;
if (isFile) {
if (createTempFiles) {
param = new FileUploadDiscResource(parameterName, tempFilesDirectory);
} else {
param = new FileUploadMemoryResource(parameterName, tempFilesDirectory);
}
} else {
param = new FileUploadValueParam(parameterName, request.getCharacterEncoding());
}
return param;
}
private Multimap readHeaders() 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();
Multimap headers = LinkedListMultimap.create();
String[] split = headersString.split("\r\n");
for (String headerString : split) {
parseParams(headerString, "; ", headers);
}
return headers;
}
return null;
}
}