blob: 2711cf43aaa5e373cb5ab1ca257e9cdcd763595b [file] [log] [blame]
/*
* Copyright 1999-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.
*/
package org.apache.cocoon.servlet.multipart;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
/**
* This class is used to implement a multipart request wrapper.
* It will parse the http post stream and and fill it's hashtable with values.
*
* The hashtable will contain:
* Vector: inline part values
* FilePart: file part
*
* @author <a href="mailto:j.tervoorde@home.nl">Jeroen ter Voorde</a>
* @version CVS $Id: MultipartParser.java,v 1.6 2004/03/05 13:02:58 bdelacretaz Exp $
*/
public class MultipartParser {
private final static int FILE_BUFFER_SIZE = 4096;
private final static int INLINE_BUFFER_SIZE = 256;
private static final int MAX_BOUNDARY_SIZE = 128;
private boolean saveUploadedFilesToDisk;
private File uploadDirectory = null;
private boolean allowOverwrite;
private boolean silentlyRename;
private int maxUploadSize;
private String characterEncoding;
private Hashtable parts;
/**
* Constructor, parses given request
*
* @param saveUploadedFilesToDisk Write fileparts to the uploadDirectory. If true the corresponding object
* in the hashtable will contain a FilePartFile, if false a FilePartArray
* @param uploadDirectory The directory to write to if saveUploadedFilesToDisk is true.
* @param allowOverwrite Allow existing files to be overwritten.
* @param silentlyRename If file exists rename file (using filename+number).
* @param maxUploadSize The maximum content length accepted.
* @param characterEncoding The character encoding to be used.
*/
public MultipartParser(boolean saveUploadedFilesToDisk,
File uploadDirectory,
boolean allowOverwrite,
boolean silentlyRename,
int maxUploadSize,
String characterEncoding)
{
this.saveUploadedFilesToDisk = saveUploadedFilesToDisk;
this.uploadDirectory = uploadDirectory;
this.allowOverwrite = allowOverwrite;
this.silentlyRename = silentlyRename;
this.maxUploadSize = maxUploadSize;
this.characterEncoding = characterEncoding;
}
public Hashtable getParts(int contentLength, String contentType, InputStream requestStream)
throws IOException, MultipartException {
if (contentLength > this.maxUploadSize) {
throw new IOException("Content length exceeds maximum upload size");
}
this.parts = new Hashtable();
BufferedInputStream bufferedStream = new BufferedInputStream(requestStream);
PushbackInputStream pushbackStream = new PushbackInputStream(bufferedStream, MAX_BOUNDARY_SIZE);
TokenStream stream = new TokenStream(pushbackStream);
parseMultiPart(stream, getBoundary(contentType));
return this.parts;
}
public Hashtable getParts(HttpServletRequest request) throws IOException, MultipartException {
return getParts(request.getContentLength(), request.getContentType(), request.getInputStream());
}
/**
* Parse a multipart block
*
* @param ts
* @param boundary
*
* @throws IOException
* @throws MultipartException
*/
private void parseMultiPart(TokenStream ts, String boundary)
throws IOException, MultipartException {
ts.setBoundary(boundary.getBytes());
ts.read(); // read first boundary away
ts.setBoundary(("\r\n" + boundary).getBytes());
while (ts.getState() == TokenStream.STATE_NEXTPART) {
ts.nextPart();
parsePart(ts);
}
if (ts.getState() != TokenStream.STATE_ENDMULTIPART) { // sanity check
throw new MultipartException("Malformed stream");
}
}
/**
* Parse a single part
*
* @param ts
*
* @throws IOException
* @throws MultipartException
*/
private void parsePart(TokenStream ts)
throws IOException, MultipartException {
Hashtable headers = new Hashtable();
headers = readHeaders(ts);
try {
if (headers.containsKey("filename")) {
if (!"".equals(headers.get("filename"))) {
parseFilePart(ts, headers);
} else {
// IE6 sends an empty part with filename="" for
// empty upload fields. Just parse away the part
byte[] buf = new byte[32];
while(ts.getState() == TokenStream.STATE_READING)
ts.read(buf);
}
} else if (((String) headers.get("content-disposition"))
.toLowerCase().equals("form-data")) {
parseInlinePart(ts, headers);
}
// FIXME: multipart/mixed parts are untested.
else if (((String) headers.get("content-disposition")).toLowerCase()
.indexOf("multipart") > -1) {
parseMultiPart(new TokenStream(ts, MAX_BOUNDARY_SIZE),
"--" + (String) headers.get("boundary"));
ts.read(); // read past boundary
} else {
throw new MultipartException("Unknown part type");
}
} catch (IOException e) {
throw new MultipartException("Malformed stream: " + e.getMessage());
} catch (NullPointerException e) {
e.printStackTrace();
throw new MultipartException("Malformed header");
}
}
/**
* Parse a file part
*
* @param in
* @param headers
*
* @throws IOException
* @throws MultipartException
*/
private void parseFilePart(TokenStream in, Hashtable headers)
throws IOException, MultipartException {
byte[] buf = new byte[FILE_BUFFER_SIZE];
OutputStream out = null;
File file = null;
if (!saveUploadedFilesToDisk) {
out = new ByteArrayOutputStream();
} else {
String fileName = (String) headers.get("filename");
if(File.separatorChar == '\\')
fileName = fileName.replace('/','\\');
else
fileName = fileName.replace('\\','/');
String filePath = uploadDirectory.getPath() + File.separator;
fileName = new File(fileName).getName();
file = new File(filePath + fileName);
if (!allowOverwrite && !file.createNewFile()) {
if (silentlyRename) {
int c = 0;
do {
file = new File(filePath + c++ + "_" + fileName);
} while (!file.createNewFile());
} else {
throw new MultipartException("Duplicate file "
+ file.getName() + ".");
}
}
out = new FileOutputStream(file);
}
int read = 0;
while (in.getState() == TokenStream.STATE_READING) { // read data
read = in.read(buf);
out.write(buf, 0, read);
}
out.close();
if (file == null) {
byte[] bytes = ((ByteArrayOutputStream) out).toByteArray();
this.parts.put(headers.get("name"),
new PartInMemory(headers, new ByteArrayInputStream(bytes),bytes.length));
} else {
this.parts.put(headers.get("name"), new PartOnDisk(headers, file));
}
}
/**
* Parse an inline part
*
* @param in
* @param headers
*
* @throws IOException
*/
private void parseInlinePart(TokenStream in, Hashtable headers)
throws IOException {
byte[] buf = new byte[INLINE_BUFFER_SIZE];
StringBuffer value = new StringBuffer();
while (in.getState() == TokenStream.STATE_READING) {
int read = in.read(buf);
value.append(new String(buf, 0, read, this.characterEncoding));
}
String field = (String) headers.get("name");
Vector v = (Vector) this.parts.get(field);
if (v == null) {
v = new Vector();
this.parts.put(field, v);
}
v.add(value.toString());
}
/**
* Read part headers
*
* @param in
*
* @return
*
* @throws IOException
*/
private Hashtable readHeaders(TokenStream in) throws IOException {
Hashtable headers = new Hashtable();
String hdrline = readln(in);
while (!"".equals(hdrline)) {
StringTokenizer tokenizer = new StringTokenizer(hdrline);
headers.put(tokenizer.nextToken(" :").toLowerCase(),
tokenizer.nextToken(" :;"));
// The extra tokenizer.hasMoreTokens() in headers.put
// handles the filename="" case IE6 submits for an empty
// upload field.
while (tokenizer.hasMoreTokens()) {
headers.put(tokenizer.nextToken(" ;=\""),
tokenizer.hasMoreTokens()?tokenizer.nextToken("=\""):"");
}
hdrline = readln(in);
}
return headers;
}
/**
* Get boundary from contentheader
*
* @param hdr
*
* @return
*/
private String getBoundary(String hdr) {
int start = hdr.toLowerCase().indexOf("boundary=");
if (start > -1) {
return "--" + hdr.substring(start + 9);
} else {
return null;
}
}
/**
* Read string until newline or end of stream
*
* @param in
*
* @return
*
* @throws IOException
*/
private String readln(TokenStream in) throws IOException {
StringBuffer out = new StringBuffer();
int b = in.read();
while ((b != -1) && (b != '\r')) {
out.append((char) b);
b = in.read();
}
if (b == '\r') {
in.read(); // read '\n'
}
return out.toString();
}
}