* Switch to Jakarta Commons FileUpload for handling file uploads.

* Introduce uploadSoftfail setting in apps.properties that allows
   file upload errors to be cought by checking req.data.helma_upload_error
This commit is contained in:
hns 2005-07-29 11:45:42 +00:00
parent 56c1973ca5
commit 878b7ee06d
13 changed files with 77 additions and 1586 deletions

View file

@ -27,6 +27,11 @@ import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileUploadBase;
/**
* This is an abstract Hop servlet adapter. This class communicates with hop applications
* via RMI. Subclasses are either one servlet per app, or one servlet that handles multiple apps
@ -43,7 +48,7 @@ public abstract class AbstractServletClient extends HttpServlet {
String hopUrl;
// limit to HTTP uploads in kB
int uploadLimit = 4096;
int uploadLimit = 1024;
// cookie domain to use
String cookieDomain;
@ -61,6 +66,10 @@ public abstract class AbstractServletClient extends HttpServlet {
// enable debug output
boolean debug;
// soft fail on file upload errors by setting flag "helma_upload_error" in RequestTrans
// if fals, an error response is written to the client immediately without entering helma
boolean uploadSoftfail = false;
/**
* Init this servlet.
*
@ -76,10 +85,13 @@ public abstract class AbstractServletClient extends HttpServlet {
try {
uploadLimit = (upstr == null) ? 1024 : Integer.parseInt(upstr);
} catch (NumberFormatException x) {
System.err.println("Bad format for uploadLimit: " + upstr);
System.err.println("Bad number format for uploadLimit: " + upstr);
uploadLimit = 1024;
}
// soft fail mode for upload errors
uploadSoftfail = ("true".equalsIgnoreCase(init.getInitParameter("uploadSoftfail")));
// get cookie domain
cookieDomain = init.getInitParameter("cookieDomain");
if (cookieDomain != null) {
@ -150,36 +162,55 @@ public abstract class AbstractServletClient extends HttpServlet {
}
}
// check for MIME file uploads
String contentType = request.getContentType();
if ((contentType != null) &&
(contentType.indexOf("multipart/form-data") == 0)) {
if (FileUpload.isMultipartContent(request)) {
// File Upload
try {
FileUpload upload = getUpload(request, encoding);
DiskFileUpload upload = new DiskFileUpload();
if (upload != null) {
Hashtable parts = upload.getParts();
upload.setSizeMax(uploadLimit * 1024);
upload.setHeaderEncoding(encoding);
for (Enumeration e = parts.keys(); e.hasMoreElements();) {
String nextKey = (String) e.nextElement();
Object nextPart = parts.get(nextKey);
List uploads = upload.parseRequest(request);
Iterator it = uploads.iterator();
if (nextPart instanceof List) {
reqtrans.set(nextKey, ((List) nextPart).get(0));
reqtrans.set(nextKey+"_array", ((List) nextPart).toArray());
} else {
reqtrans.set(nextKey, nextPart);
}
while (it.hasNext()) {
FileItem item = (FileItem) it.next();
// TODO: set fieldname_array if multiple values for one fieldname
String name = item.getFieldName();
Object value = null;
// check if this is an ordinary HTML form element or a file upload
if (item.isFormField()) {
value = item.getString(encoding);
} else {
value = new MimePart(item.getName(),
item.get(),
item.getContentType());
}
// if multiple values exist for this name, append to _array
if (reqtrans.get(name) != null) {
appendFormValue(reqtrans, name, value);
} else {
reqtrans.set(name, value);
}
}
} catch (Exception upx) {
sendError(response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE,
"Sorry, upload size exceeds limit of " + uploadLimit +
"kB.");
return;
} catch (Exception upx) {
System.err.println("Error in file upload: " + upx);
if (uploadSoftfail) {
String msg = upx.getMessage();
if (msg == null || msg.length() == 0) {
msg = upx.toString();
}
reqtrans.set("helma_upload_error", msg);
} else if (upx instanceof FileUploadBase.SizeLimitExceededException) {
sendError(response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE,
"File upload size exceeds limit of " + uploadLimit + "kB");
return;
} else {
sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Error in file upload: " + upx);
return;
}
}
}
@ -487,24 +518,29 @@ public abstract class AbstractServletClient extends HttpServlet {
}
}
FileUpload getUpload(HttpServletRequest request, String encoding) throws Exception {
int contentLength = request.getContentLength();
BufferedInputStream in = new BufferedInputStream(request.getInputStream());
if (contentLength > (uploadLimit * 1024)) {
throw new RuntimeException("Upload exceeds limit of " + uploadLimit + " kb.");
/**
* Used to build the form value array when a multipart (file upload) form has
* multiple values for one form element name.
*
* @param reqtrans
* @param name
* @param value
*/
private void appendFormValue(RequestTrans reqtrans, String name, Object value) {
String arrayName = name + "_array";
try {
Object[] values = (Object[]) reqtrans.get(arrayName);
if (values == null) {
reqtrans.set(arrayName, new Object[] {reqtrans.get(name), value});
} else {
Object[] newValues = new Object[values.length + 1];
System.arraycopy(values, 0, newValues, 0, values.length);
newValues[values.length] = value;
reqtrans.set(arrayName, newValues);
}
} catch (ClassCastException x) {
// name_array is defined as something else in the form - don't overwrite it
}
String contentType = request.getContentType();
FileUpload upload = new FileUpload(uploadLimit);
upload.load(in, contentType, contentLength, encoding);
return upload;
}
Object getUploadPart(FileUpload upload, String name) {
return upload.getParts().get(name);
}
/**

View file

@ -1,151 +0,0 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.util;
import helma.util.mime.*;
import java.io.*;
import java.util.*;
/**
* Utility class for MIME file uploads via HTTP POST.
*/
public class FileUpload {
public Hashtable parts;
int maxKbytes;
/**
* Creates a new FileUpload object.
*/
public FileUpload() {
maxKbytes = 4096;
}
/**
* Creates a new FileUpload object.
*
* @param max ...
*/
public FileUpload(int max) {
maxKbytes = max;
}
/**
*
*
* @return ...
*/
public Hashtable getParts() {
return parts;
}
/**
*
*
* @param is ...
* @param contentType ...
* @param contentLength ...
*
* @throws Exception ...
* @throws MimeParserException ...
* @throws IOException ...
*/
public void load(InputStream is, String contentType, int contentLength, String encoding)
throws Exception {
parts = new Hashtable();
String boundary = MimePart.getSubHeader(contentType, "boundary");
if (boundary == null) {
throw new MimeParserException("Error parsing MIME input stream.");
}
if ((maxKbytes > -1) && (contentLength > (maxKbytes * 1024))) {
throw new IOException("Size of upload exceeds limit of " + maxKbytes +
" kB.");
}
byte[] b = new byte[contentLength];
MultipartInputStream in = new MultipartInputStream(new BufferedInputStream(is),
boundary.getBytes());
while (in.nextInputStream()) {
MimeParser parser = new MimeParser(in, new MimeHeadersFactory());
MimeHeaders headers = (MimeHeaders) parser.parse();
InputStream bodystream = parser.getInputStream();
int read;
int count = 0;
while ((read = bodystream.read(b, count, 4096)) > -1) {
count += read;
if (count == b.length) {
byte[] newb = new byte[count + 4096];
System.arraycopy(b, 0, newb, 0, count);
b = newb;
}
}
byte[] newb = new byte[count];
System.arraycopy(b, 0, newb, 0, count);
String type = headers.getValue("Content-Type");
String disposition = headers.getValue("Content-Disposition");
String name = MimePart.getSubHeader(disposition, "name");
String filename = MimePart.getSubHeader(disposition, "filename");
if (filename != null) {
int sep = filename.lastIndexOf("\\");
if (sep > -1) {
filename = filename.substring(sep + 1);
}
sep = filename.lastIndexOf("/");
if (sep > -1) {
filename = filename.substring(sep + 1);
}
}
Object existingValue = parts.get(name);
Object newValue = null;
if (filename != null) {
newValue = new MimePart(filename, newb, type);
} else {
newValue = new String(newb, encoding);
}
if (existingValue == null) {
// no previous value, just add new object
parts.put(name, newValue);
} else if (existingValue instanceof ArrayList) {
// already multiple values, add to list
((ArrayList) existingValue).add(newValue);
} else {
// already one value, convert to list
ArrayList list = new ArrayList();
list.add(existingValue);
list.add(newValue);
parts.put(name, list);
}
}
}
}

View file

@ -1,161 +0,0 @@
// LanguageTag.java
// $Id$
// (c) COPYRIGHT MIT, INRIA and Keio, 1999
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.util.*;
import java.io.*;
/**
* This class is used to represent parsed Language tags,
* It creates a representation from a string based representation
* of the Language tag, as defined in RFC 1766
* NOTE, we don't check that languages are defined according to ISO 639
*/
public class LanguageTag implements Serializable, Cloneable {
public static int NO_MATCH = -1;
public static int MATCH_LANGUAGE = 1;
public static int MATCH_SPECIFIC_LANGUAGE = 2;
// subtag is not dialect as subtype can be
// dialect or country identification or script variation, etc...
public static int MATCH_SUBTAG = 3;
public static int MATCH_SPECIFIC_SUBTAG = 4;
/**
* String representation of the language
*
* @serial
*/
protected String language = null ;
/**
* String representation of subtag
*
* @serial
*/
protected String subtag = null ;
/**
* external form of this language tag
*
* @serial
*/
protected String external = null ;
/**
* How good the given LanguageTag matches the receiver of the method ?
* This method returns a matching level among:
* <dl>
* <dt>NO_MATCH<dd>Language not matching,</dd>
* <dt>MATCH_LANGUAGE<dd>Languages match roughly (with *),</dd>
* <dt>MATCH_SPECIFIC_LANGUAGE<dd>Languages match exactly,</dd>
* <dt>MATCH_SUBTAG<dd>Languages match, subtags matches roughly</dd>
* <dt>MATCH_SPECIFIC_SUBAG<dd>Languages match, subtag matches exactly</dd>
* </dl>
* The matches are ranked from worst match to best match, a simple
* Max ( match[i], matched) will give the best match.
* @param other The other LanguageTag to match against ourself.
*/
public int match (LanguageTag other) {
int match = NO_MATCH;
// match types:
if ( language.equals("*") || other.language.equals("*") ) {
match = MATCH_LANGUAGE;
} else if ( ! language.equalsIgnoreCase(other.language) ) {
return NO_MATCH ;
} else {
match = MATCH_SPECIFIC_LANGUAGE;
}
// match subtypes:
if ((subtag == null) || (other.subtag == null))
return match;
if ( subtag.equals("*") || other.subtag.equals("*") ) {
match = MATCH_SUBTAG ;
} else if ( ! subtag.equalsIgnoreCase(other.subtag) ) {
return NO_MATCH;
} else {
match = MATCH_SPECIFIC_SUBTAG;
}
return match;
}
/**
* A printable representation of this LanguageTag.
* The printed representation is guaranteed to be parseable by the
* String constructor.
*/
public String toString () {
if ( external == null ) {
if (subtag != null) {
external = language + "-" + subtag;
} else {
external = language;
}
}
return external ;
}
/**
* Get the language
* @return The language, encoded as a String.
*/
public String getLanguage() {
return language;
}
/**
* Get the subtag
* @return The subtag, encoded as a string
*/
public String getSubtag() {
return language;
}
/**
* Construct a Language tag from a spec
* @param spec A string representing a LangateTag
*/
public LanguageTag(String spec) {
int strl = spec.length() ;
int start = 0, look = -1 ;
// skip leading/trailing blanks:
while ((start < strl) && (spec.charAt (start)) <= ' ')
start++ ;
while ((strl > start) && (spec.charAt (strl-1) <= ' '))
strl-- ;
// get the type:
StringBuffer sb = new StringBuffer () ;
while ((start < strl) && ((look = spec.charAt(start)) != '-')
&& ((look = spec.charAt(start)) != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.language = sb.toString() ;
if ( look == '-' ) {
start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look = spec.charAt(start)) > ' ') && (look != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.subtag = sb.toString() ;
}
}
/**
* construct directly a language tag
* it NEEDS both language and subtype parameters
*/
public LanguageTag(String language, String subtag) {
this.language = language;
this.subtag = subtag;
}
}

View file

@ -1,51 +0,0 @@
// MimeHeaderHolder.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.io.*;
public interface MimeHeaderHolder {
/**
* A new header has been parsed.
* @param name The name of the encountered header.
* @param buf The byte buffer containing the value.
* @param off Offset of the header value in the above buffer.
* @param len Length of the value in the above header.
* @exception MimeParserException if the parsing failed
*/
public void notifyHeader(String name, byte buf[], int off, int len)
throws MimeParserException;
/**
* The parsing is now about to start, take any appropriate action.
* This hook can return a <strong>true</strong> boolean value to enforce
* the MIME parser into transparent mode (eg the parser will <em>not</em>
* try to parse any headers.
* <p>This hack is primarily defined for HTTP/0.9 support, it might
* also be usefull for other hacks.
* @param parser The Mime parser.
* @return A boolean <strong>true</strong> if the MimeParser shouldn't
* continue the parsing, <strong>false</strong> otherwise.
* @exception MimeParserException if the parsing failed
* @exception IOException if an IO error occurs.
*/
public boolean notifyBeginParsing(MimeParser parser)
throws MimeParserException, IOException;
/**
* All the headers have been parsed, take any appropriate actions.
* @param parser The Mime parser.
* @exception MimeParserException if the parsing failed
* @exception IOException if an IO error occurs.
*/
public void notifyEndParsing(MimeParser parser)
throws MimeParserException, IOException;
}

View file

@ -1,147 +0,0 @@
// MimeHeaders.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.io.*;
import java.util.*;
/**
* The most stupid MIME header holder.
* This class uses a hashtable mapping header names (as String), to header
* values (as String). Header names are lowered before entering the hashtable.
*/
public class MimeHeaders implements MimeHeaderHolder {
Hashtable headers = null;
MimeParser parser = null;
/**
* A new header has been parsed.
* @param name The name of the encountered header.
* @param buf The byte buffer containing the value.
* @param off Offset of the header value in the above buffer.
* @param len Length of the value in the above header.
* @exception MimeParserException if the parsing failed
*/
public void notifyHeader(String name, byte buf[], int off, int len)
throws MimeParserException
{
String lname = name.toLowerCase();
String oldval = null;
if ( headers == null ) {
headers = new Hashtable(5);
} else {
oldval = (String) headers.get(lname);
}
String newval = ((oldval != null)
? oldval + "," + new String(buf, 0, off, len)
: new String(buf, 0, off, len));
headers.put(lname, newval);
}
/**
* The parsing is now about to start, take any appropriate action.
* This hook can return a <strong>true</strong> boolean value to enforce
* the MIME parser into transparent mode (eg the parser will <em>not</em>
* try to parse any headers.
* <p>This hack is primarily defined for HTTP/0.9 support, it might
* also be usefull for other hacks.
* @param parser The Mime parser.
* @return A boolean <strong>true</strong> if the MimeParser shouldn't
* continue the parsing, <strong>false</strong> otherwise.
* @exception IOException if an IO error occurs.
*/
public boolean notifyBeginParsing(MimeParser parser)
throws IOException
{
return false;
}
/**
* All the headers have been parsed, take any appropriate actions.
* @param parser The Mime parser.
* @exception IOException if an IO error occurs.
*/
public void notifyEndParsing(MimeParser parser)
throws IOException
{
return;
}
/**
* Set a header value.
* @param name The header name.
* @param value The header value.
*/
public void setValue(String name, String value) {
if ( headers == null )
headers = new Hashtable(5);
headers.put(name.toLowerCase(), value);
}
/**
* Retreive a header value.
* @param name The name of the header.
* @return The value for this header, or <strong>null</strong> if
* undefined.
*/
public String getValue(String name) {
return ((headers != null)
? (String) headers.get(name.toLowerCase())
: null);
}
/**
* Enumerate the headers defined by the holder.
* @return A enumeration of header names, or <strong>null</strong> if no
* header is defined.
*/
public Enumeration enumerateHeaders() {
if ( headers == null )
return null;
return headers.keys();
}
/**
* Get the entity stream attached to these headers, if any.
* @return An InputStream instance, or <strong>null</strong> if no
* entity available.
*/
public InputStream getInputStream() {
return ((parser != null) ? parser.getInputStream() : null);
}
/**
* Dump all headers to the given stream.
* @param out The stream to dump to.
*/
public void dump(PrintStream out) {
Enumeration names = enumerateHeaders();
if ( names != null ) {
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
out.println(name+": "+headers.get(name));
}
}
}
public MimeHeaders(MimeParser parser) {
this.parser = parser;
}
public MimeHeaders() {
}
}

View file

@ -1,25 +0,0 @@
// MimeHeadersFactory.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
/**
* A Mime header factory, that will build instances of the MimeHeaders class
* to hold MIME headers.
*/
public class MimeHeadersFactory implements MimeParserFactory {
/**
* Create a new header holder to hold the parser's result.
* @param parser The parser that has something to parse.
* @return A MimeParserHandler compliant object.
*/
public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
return new MimeHeaders(parser);
}
}

View file

@ -1,228 +0,0 @@
// MimeParser.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.util.*;
import java.io.* ;
/**
* The MimeParser class parses an input MIME stream.
*/
public class MimeParser {
protected int ch = -1 ;
protected InputStream input = null ;
protected byte buffer[] = new byte[128] ;
protected int bsize = 0 ;
/**
* The factory used to create new MIME header holders.
*/
protected MimeParserFactory factory = null ;
protected void expect (int car)
throws MimeParserException, IOException
{
if ( car != ch ) {
String sc = (new Character((char) car)).toString() ;
String se = (new Character((char) ch)).toString() ;
throw new MimeParserException ("expecting "
+ sc + "("+car+")"
+ " got "
+ se + "("+ch+")\n"
+ "context: "
+ new String (buffer, 0, 0, bsize)
+ "\n") ;
}
ch = input.read() ;
}
protected void skipSpaces ()
throws MimeParserException, IOException
{
while ( (ch == ' ') || (ch == '\t') )
ch = input.read() ;
}
protected final void append (int c) {
if ( bsize+1 >= buffer.length ) {
byte nb[] = new byte[buffer.length*2] ;
System.arraycopy (buffer, 0, nb, 0, buffer.length) ;
buffer = nb ;
}
buffer[bsize++] = (byte) c ;
}
/*
* Get the header name:
*/
protected String parse822HeaderName ()
throws MimeParserException, IOException
{
bsize = 0 ;
while ( (ch >= 32) && (ch != ':') ) {
append ((char) ch) ;
ch = input.read() ;
}
expect (':') ;
if ( bsize <= 0 )
throw new MimeParserException ("expected a header name.") ;
return new String (buffer, 0, 0, bsize) ;
}
/*
* Get the header body, still trying to be 822 compliant *and* HTTP
* robust, which is unfortunatelly a contrdiction.
*/
protected void parse822HeaderBody ()
throws MimeParserException, IOException
{
bsize = 0 ;
skipSpaces () ;
loop:
while ( true ) {
switch (ch) {
case -1:
break loop ;
case '\r':
if ( (ch = input.read()) != '\n' ) {
append ('\r') ;
continue ;
}
// no break intentional
case '\n':
// do as if '\r' had been received. This defeats 822, but
// makes HTTP more "robust". I wish HTTP were a binary
// protocol.
switch (ch = input.read()) {
case ' ': case '\t':
do {
ch = input.read () ;
} while ((ch == ' ') || (ch == '\t')) ;
append(ch);
break ;
default:
break loop ;
}
break ;
default:
append ((char) ch) ;
break ;
}
ch = input.read() ;
}
return ;
}
/*
* Parse the given input stream for an HTTP 1.1 token.
*/
protected String parseToken (boolean lower)
throws MimeParserException, IOException
{
bsize = 0 ;
while ( true ) {
switch ( ch ) {
// CTLs
case -1:
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
case 8: case 9: case 10: case 11: case 12: case 13: case 14:
case 15: case 16: case 17: case 18: case 19: case 20: case 21:
case 22: case 23: case 24: case 25: case 26: case 27: case 28:
case 29: case 30: case 31:
// tspecials
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '\"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ':
return new String (buffer, 0, 0, bsize) ;
default:
append ((char) (lower
? Character.toLowerCase((char) ch)
: ch)) ;
}
ch = input.read() ;
}
}
protected void parse822Headers(MimeHeaderHolder msg)
throws MimeParserException, IOException
{
while ( true ) {
if ( ch == '\r' ) {
if ( (ch = input.read()) == '\n' )
return ;
} else if ( ch == '\n' ) {
return ;
}
String name = parse822HeaderName () ;
skipSpaces() ;
parse822HeaderBody () ;
msg.notifyHeader(name, buffer, 0, bsize);
}
}
public MimeHeaderHolder parse()
throws MimeParserException, IOException
{
MimeHeaderHolder msg = factory.createHeaderHolder(this);
ch = input.read() ;
cached = true ;
if ( ! msg.notifyBeginParsing(this) ) {
if ( ! cached )
ch = input.read();
parse822Headers (msg) ;
}
msg.notifyEndParsing(this);
return msg;
}
boolean cached = false ;
public int read()
throws IOException
{
if ( cached )
cached = false;
else
ch = input.read();
return ch;
}
public void unread(int ch) {
if ( cached )
throw new RuntimeException("cannot unread more then once !");
this.ch = ch;
cached = true;
}
/**
* Get the message body, as an input stream.
* @return The input stream used by the parser to get data, after
* a call to <code>parse</code>, this input stream contains exactly
* the body of the message.
*/
public InputStream getInputStream () {
return input ;
}
/**
* Create an instance of the MIMEParser class.
* @param input The input stream to be parsed as a MIME stream.
* @param factory The factory used to create MIME header holders.
*/
public MimeParser (InputStream input, MimeParserFactory factory) {
this.input = input ;
this.factory = factory;
}
}

View file

@ -1,14 +0,0 @@
// MimeParserException.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
public class MimeParserException extends Exception {
public MimeParserException(String msg) {
super(msg);
}
}

View file

@ -1,24 +0,0 @@
// MimeParserFactory.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
/**
* This class is used by the MimeParser, to create new MIME message holders.
* Each MIME parse instances is custmozied wit hits own factory, which it
* will use to create MIME header holders.
*/
public interface MimeParserFactory {
/**
* Create a new header holder to hold the parser's result.
* @param parser The parser that has something to parse.
* @return A MimeParserHandler compliant object.
*/
abstract public MimeHeaderHolder createHeaderHolder(MimeParser parser);
}

View file

@ -1,373 +0,0 @@
// MimeType.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.util.*;
import java.io.*;
/**
* This class is used to represent parsed MIME types.
* It creates this representation from a string based representation of
* the MIME type, as defined in the RFC 1345.
*/
public class MimeType implements Serializable, Cloneable {
/**
* List of well known MIME types:
*/
public static MimeType TEXT_HTML = null ;
public static MimeType APPLICATION_POSTSCRIPT = null ;
public static MimeType TEXT_PLAIN = null ;
public static MimeType APPLICATION_X_WWW_FORM_URLENCODED = null ;
public static MimeType MULTIPART_FORM_DATA = null ;
public static MimeType APPLICATION_X_JAVA_AGENT = null ;
public static MimeType MESSAGE_HTTP = null ;
public static MimeType TEXT_CSS = null ;
public static MimeType TEXT = null ;
static {
try {
TEXT_HTML
= new MimeType("text/html");
APPLICATION_POSTSCRIPT
= new MimeType ("application/postscript") ;
TEXT_PLAIN
= new MimeType("text/plain") ;
APPLICATION_X_WWW_FORM_URLENCODED
= new MimeType("application/x-www-form-urlencoded") ;
MULTIPART_FORM_DATA
= new MimeType("multipart/form-data") ;
APPLICATION_X_JAVA_AGENT
= new MimeType("application/x-java-agent") ;
MESSAGE_HTTP
= new MimeType("message/http");
TEXT_CSS
= new MimeType("text/css");
TEXT
= new MimeType("text/*");
} catch (MimeTypeFormatException e) {
System.out.println ("httpd.MimeType: invalid static init.") ;
System.exit (1) ;
}
}
public final static int NO_MATCH = -1 ;
public final static int MATCH_TYPE = 1 ;
public final static int MATCH_SPECIFIC_TYPE = 2 ;
public final static int MATCH_SUBTYPE = 3 ;
public final static int MATCH_SPECIFIC_SUBTYPE = 4 ;
/**
* String representation of type
*
* @serial
*/
protected String type = null ;
/**
* String representation of subtype
*
* @serial
*/
protected String subtype = null ;
/**
* parameter names
*
* @serial
*/
protected String pnames[] = null;
/**
* parameter values
*
* @serial
*/
protected String pvalues[] = null;
/**
* external form of this mime type
*
* @serial
*/
protected String external = null ;
/**
* How good the given MimeType matches the receiver of the method ?
* This method returns a matching level among:
* <dl>
* <dt>NO_MATCH<dd>Types not matching,</dd>
* <dt>MATCH_TYPE<dd>Types match,</dd>
* <dt>MATCH_SPECIFIC_TYPE<dd>Types match exactly,</dd>
* <dt>MATCH_SUBTYPE<dd>Types match, subtypes matches too</dd>
* <dt>MATCH_SPECIFIC_SUBTYPE<dd>Types match, subtypes matches exactly</dd>
* </dl>
* The matches are ranked from worst match to best match, a simple
* Max ( match[i], matched) will give the best match.
* @param other The other MimeType to match against ourself.
*/
public int match (MimeType other) {
int match = NO_MATCH;
// match types:
if ( type.equals("*") || other.type.equals("*") ) {
return MATCH_TYPE;
} else if ( ! type.equals (other.type) ) {
return NO_MATCH ;
} else {
match = MATCH_SPECIFIC_TYPE;
}
// match subtypes:
if ( subtype.equals("*") || other.subtype.equals("*") ) {
match = MATCH_SUBTYPE ;
} else if ( ! subtype.equals (other.subtype) ) {
return NO_MATCH;
} else {
match = MATCH_SPECIFIC_SUBTYPE;
}
return match;
}
/**
* A printable representation of this MimeType.
* The printed representation is guaranteed to be parseable by the
* String constructor.
*/
public String toString () {
if ( external == null ) {
StringBuffer sb = new StringBuffer (type) ;
sb.append((char) '/') ;
sb.append (subtype) ;
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length; i++) {
sb.append(';');
sb.append(pnames[i]);
if ( pvalues[i] != null ) {
sb.append('=');
sb.append(pvalues[i]);
}
}
}
external = sb.toString() ;
}
return external ;
}
/**
* Does this MIME type has some value for the given parameter ?
* @param name The parameter to check.
* @return <strong>True</strong> if this parameter has a value, false
* otherwise.
*/
public boolean hasParameter (String name) {
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length ; i++) {
if ( pnames[i].equals(name) )
return true ;
}
}
return false ;
}
/**
* Get the major type of this mime type.
* @return The major type, encoded as a String.
*/
public String getType() {
return type;
}
/**
* Get the minor type (subtype) of this mime type.
* @return The minor or subtype encoded as a String.
*/
public String getSubtype() {
return subtype;
}
/**
* Get a mime type parameter value.
* @param name The parameter whose value is to be returned.
* @return The parameter value, or <b>null</b> if not found.
*/
public String getParameterValue (String name) {
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length ; i++) {
if ( pnames[i].equals(name) )
return pvalues[i];
}
}
return null ;
}
/**
* adds some parameters to a MimeType
* @param param a String array of parameter names
* @param values the corresponding String array of values
*/
public void addParameters(String[] param, String[] values) {
// sanity check
if ((param == null) || (values == null) ||
(values.length != param.length))
return;
if (pnames == null) {
pnames = param;
pvalues = values;
} else {
String[] nparam = new String[pnames.length+param.length];
String[] nvalues = new String[pvalues.length+values.length];
System.arraycopy(pnames, 0, nparam, 0, pnames.length);
System.arraycopy(param, 0, nparam, pnames.length, param.length);
System.arraycopy(pvalues, 0, nvalues, 0, pvalues.length);
System.arraycopy(values,0, nvalues, pvalues.length, values.length);
pnames = nparam;
pvalues = nvalues;
}
external = null;
}
/**
* get a clone of this object
* @return another cloned instance of MimeType
*/
public MimeType getClone() {
try {
return (MimeType) clone();
} catch (CloneNotSupportedException ex) {
// should never happen as we are Cloneable!
}
// never reached
return null;
}
/**
* adds a parameterto a MimeType
* @param param the parameter name, as a String
* @param value the parameter value, as a Sting
*/
public void addParameter(String param, String value) {
String[] p = new String[1];
String[] v = new String[1];
p[0] = param;
v[0] = value;
addParameters(p, v);
}
/**
* Construct MimeType object for the given string.
* The string should be the representation of the type. This methods
* tries to be compliant with HTTP1.1, p 15, although it is not
* (because of quoted-text not being accepted).
* FIXME
* @param spec A string representing a MimeType
* @exception MimeTypeFormatException if the string couldn't be parsed.
*/
public MimeType (String spec)
throws MimeTypeFormatException
{
int strl = spec.length() ;
int start = 0, look = -1 ;
// skip leading/trailing blanks:
while ((start < strl) && (spec.charAt (start)) <= ' ')
start++ ;
while ((strl > start) && (spec.charAt (strl-1) <= ' '))
strl-- ;
// get the type:
StringBuffer sb = new StringBuffer () ;
while ((start < strl) && ((look = spec.charAt(start)) != '/')) {
sb.append ((char) look) ;
start++ ;
}
if ( look != '/' )
throw new MimeTypeFormatException (spec) ;
this.type = sb.toString() ;
// get the subtype:
start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look = spec.charAt(start)) > ' ') && (look != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.subtype = sb.toString() ;
// get parameters, if any:
while ((start < strl) && ((look = spec.charAt(start)) <= ' '))
start++ ;
if ( start < strl ) {
if (spec.charAt(start) != ';')
throw new MimeTypeFormatException (spec) ;
start++ ;
Vector vp = new Vector(4) ;
Vector vv = new Vector(4) ;
while ( start < strl ) {
while ((start < strl) && (spec.charAt(start) <= ' ')) start++ ;
// get parameter name:
sb.setLength (0) ;
while ((start < strl)
&& ((look=spec.charAt(start)) > ' ') && (look != '=')) {
sb.append (Character.toLowerCase((char) look)) ;
start++ ;
}
String name = sb.toString() ;
// get the value:
while ((start < strl) && (spec.charAt(start) <= ' ')) start++ ;
if (spec.charAt(start) != '=')
throw new MimeTypeFormatException (spec) ;
start++ ;
while ((start < strl) &&
((spec.charAt(start) == '"') ||
(spec.charAt(start) <= ' '))) start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look=spec.charAt(start)) > ' ')
&& (look != ';')
&& (look != '"')) {
sb.append ((char) look) ;
start++ ;
}
while ((start < strl) && (spec.charAt(start) != ';')) start++ ;
start++ ;
String value = sb.toString() ;
vp.addElement(name);
vv.addElement(value);
}
this.pnames = new String[vp.size()];
vp.copyInto(pnames);
this.pvalues = new String[vv.size()];
vv.copyInto(pvalues);
}
}
public MimeType (String type, String subtype
, String pnames[], String pvalues[]) {
this.type = type ;
this.subtype = subtype ;
this.pnames = pnames;
this.pvalues = pvalues;
}
public MimeType (String type, String subtype) {
this.type = type;
this.subtype = subtype;
}
public static void main (String args[]) {
if ( args.length == 1) {
MimeType type = null ;
try {
type = new MimeType (args[0]) ;
} catch (MimeTypeFormatException e) {
}
if ( type != null )
System.out.println (type) ;
else
System.out.println ("Invalid mime type specification.") ;
} else {
System.out.println ("Usage: java MimeType <type-to-parse>") ;
}
}
}

View file

@ -1,14 +0,0 @@
// MimeType.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
public class MimeTypeFormatException extends Exception {
public MimeTypeFormatException(String msg) {
super(msg);
}
}

View file

@ -1,214 +0,0 @@
// MultipartInputStream.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.io.* ;
/**
* A class to handle multipart MIME input streams. See RC 1521.
* This class handles multipart input streams, as defined by the RFC 1521.
* It prvides a sequential interface to all MIME parts, and for each part
* it delivers a suitable InputStream for getting its body.
*/
public class MultipartInputStream extends InputStream {
InputStream in = null;
byte boundary[] = null ;
byte buffer[] = null ;
boolean partEnd = false ;
boolean fileEnd = false ;
// Read boundary bytes of input in buffer
// Return true if enough bytes available, false otherwise.
private final boolean readBoundaryBytes()
throws IOException
{
int pos = 0;
while ( pos < buffer.length ) {
int got = in.read(buffer, pos, buffer.length-pos);
if ( got < 0 )
return false;
pos += got;
}
return true;
}
// Skip to next input boundary, set stream at begining of content:
// Returns true if boundary was found, false otherwise.
protected boolean skipToBoundary()
throws IOException
{
int ch = in.read() ;
skip:
while ( ch != -1 ) {
if ( ch != '-' ) {
ch = in.read() ;
continue ;
}
if ((ch = in.read()) != '-')
continue ;
in.mark(boundary.length) ;
if ( ! readBoundaryBytes() ) {
in.reset();
ch = in.read();
continue skip;
}
for (int i = 0 ; i < boundary.length ; i++) {
if ( buffer[i] != boundary[i] ) {
in.reset() ;
ch = in.read() ;
continue skip ;
}
}
// FIXME: should we check for a properly syntaxed part, which
// means that we should expect '\r\n'. For now, we just skip
// as much as we can.
if ( (ch = in.read()) == '\r' ) {
ch = in.read() ;
}
in.mark(3);
if( in.read() == '-' ) { // check fileEnd!
if( in.read() == '\r' && in.read() == '\n' ) {
fileEnd = true ;
return false ;
}
}
in.reset();
return true ;
}
fileEnd = true ;
return false ;
}
/**
* Read one byte of data from the current part.
* @return A byte of data, or <strong>-1</strong> if end of file.
* @exception IOException If some IO error occured.
*/
public int read()
throws IOException
{
int ch ;
if ( partEnd )
return -1 ;
switch (ch = in.read()) {
case '\r':
// check for a boundary
in.mark(boundary.length+3) ;
int c1 = in.read() ;
int c2 = in.read() ;
int c3 = in.read() ;
if ((c1 == '\n') && (c2 == '-') && (c3 == '-')) {
if ( ! readBoundaryBytes() ) {
in.reset();
return ch;
}
for (int i = 0 ; i < boundary.length ; i++) {
if ( buffer[i] != boundary[i] ) {
in.reset() ;
return ch ;
}
}
partEnd = true ;
if ( (ch = in.read()) == '\r' ) {
in.read() ;
} else if (ch == '-') {
// FIXME, check the second hyphen
if (in.read() == '-')
fileEnd = true ;
} else {
fileEnd = (ch == -1);
}
return -1 ;
} else {
in.reset () ;
return ch ;
}
// not reached
case -1:
fileEnd = true ;
return -1 ;
default:
return ch ;
}
}
/**
* Read n bytes of data from the current part.
* @return the number of bytes data, read or <strong>-1</strong>
* if end of file.
* @exception IOException If some IO error occured.
*/
public int read (byte b[], int off, int len)
throws IOException
{
int got = 0 ;
int ch ;
while ( got < len ) {
if ((ch = read()) == -1)
return (got == 0) ? -1 : got ;
b[off+(got++)] = (byte) (ch & 0xFF) ;
}
return got ;
}
public long skip (long n)
throws IOException
{
while ((--n >= 0) && (read() != -1))
;
return n ;
}
public int available ()
throws IOException
{
return in.available();
}
/**
* Switch to the next available part of data.
* One can interrupt the current part, and use this method to switch
* to next part before current part was totally read.
* @return A boolean <strong>true</strong> if there next partis ready,
* or <strong>false</strong> if this was the last part.
*/
public boolean nextInputStream()
throws IOException
{
if ( fileEnd ) {
return false ;
}
if ( ! partEnd ) {
return skipToBoundary() ;
} else {
partEnd = false ;
return true ;
}
}
/**
* Construct a new multipart input stream.
* @param in The initial (multipart) input stream.
* @param boundary The input stream MIME boundary.
*/
public MultipartInputStream (InputStream in, byte boundary[]) {
this.in = (in.markSupported()
? in
: new BufferedInputStream(in, boundary.length+4));
this.boundary = boundary ;
this.buffer = new byte[boundary.length] ;
this.partEnd = false ;
this.fileEnd = false ;
}
}

View file

@ -1,143 +0,0 @@
// Utils.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1998.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.util.mime;
import java.util.Hashtable;
/**
* @version $Revision$
* @author Benoit Mahe (bmahe@w3.org)
*/
public class Utils {
private static Hashtable extension_map = new Hashtable();
private static void setSuffix(String ext, String ct) {
extension_map.put(ext, ct);
}
static {
setSuffix("", "content/unknown");
setSuffix(".uu", "application/octet-stream");
setSuffix(".saveme", "application/octet-stream");
setSuffix(".dump", "application/octet-stream");
setSuffix(".hqx", "application/octet-stream");
setSuffix(".arc", "application/octet-stream");
setSuffix(".o", "application/octet-stream");
setSuffix(".a", "application/octet-stream");
setSuffix(".bin", "application/octet-stream");
setSuffix(".exe", "application/octet-stream");
setSuffix(".z", "application/octet-stream");
setSuffix(".gz", "application/octet-stream");
setSuffix(".oda", "application/oda");
setSuffix(".pdf", "application/pdf");
setSuffix(".eps", "application/postscript");
setSuffix(".ai", "application/postscript");
setSuffix(".ps", "application/postscript");
setSuffix(".rtf", "application/rtf");
setSuffix(".dvi", "application/x-dvi");
setSuffix(".hdf", "application/x-hdf");
setSuffix(".latex", "application/x-latex");
setSuffix(".cdf", "application/x-netcdf");
setSuffix(".nc", "application/x-netcdf");
setSuffix(".tex", "application/x-tex");
setSuffix(".texinfo", "application/x-texinfo");
setSuffix(".texi", "application/x-texinfo");
setSuffix(".t", "application/x-troff");
setSuffix(".tr", "application/x-troff");
setSuffix(".roff", "application/x-troff");
setSuffix(".man", "application/x-troff-man");
setSuffix(".me", "application/x-troff-me");
setSuffix(".ms", "application/x-troff-ms");
setSuffix(".src", "application/x-wais-source");
setSuffix(".wsrc", "application/x-wais-source");
setSuffix(".zip", "application/zip");
setSuffix(".bcpio", "application/x-bcpio");
setSuffix(".cpio", "application/x-cpio");
setSuffix(".gtar", "application/x-gtar");
setSuffix(".shar", "application/x-shar");
setSuffix(".sh", "application/x-shar");
setSuffix(".sv4cpio", "application/x-sv4cpio");
setSuffix(".sv4crc", "application/x-sv4crc");
setSuffix(".tar", "application/x-tar");
setSuffix(".ustar", "application/x-ustar");
setSuffix(".snd", "audio/basic");
setSuffix(".au", "audio/basic");
setSuffix(".aifc", "audio/x-aiff");
setSuffix(".aif", "audio/x-aiff");
setSuffix(".aiff", "audio/x-aiff");
setSuffix(".wav", "audio/x-wav");
setSuffix(".gif", "image/gif");
setSuffix(".ief", "image/ief");
setSuffix(".jfif", "image/jpeg");
setSuffix(".jfif-tbnl", "image/jpeg");
setSuffix(".jpe", "image/jpeg");
setSuffix(".jpg", "image/jpeg");
setSuffix(".jpeg", "image/jpeg");
setSuffix(".tif", "image/tiff");
setSuffix(".tiff", "image/tiff");
setSuffix(".ras", "image/x-cmu-rast");
setSuffix(".pnm", "image/x-portable-anymap");
setSuffix(".pbm", "image/x-portable-bitmap");
setSuffix(".pgm", "image/x-portable-graymap");
setSuffix(".ppm", "image/x-portable-pixmap");
setSuffix(".rgb", "image/x-rgb");
setSuffix(".xbm", "image/x-xbitmap");
setSuffix(".xpm", "image/x-xpixmap");
setSuffix(".xwd", "image/x-xwindowdump");
setSuffix(".htm", "text/html");
setSuffix(".html", "text/html");
setSuffix(".text", "text/plain");
setSuffix(".c", "text/plain");
setSuffix(".cc", "text/plain");
setSuffix(".c++", "text/plain");
setSuffix(".h", "text/plain");
setSuffix(".pl", "text/plain");
setSuffix(".txt", "text/plain");
setSuffix(".java", "text/plain");
setSuffix(".rtx", "application/rtf");
setSuffix(".tsv", "texyt/tab-separated-values");
setSuffix(".etx", "text/x-setext");
setSuffix(".mpg", "video/mpeg");
setSuffix(".mpe", "video/mpeg");
setSuffix(".mpeg", "video/mpeg");
setSuffix(".mov", "video/quicktime");
setSuffix(".qt", "video/quicktime");
setSuffix(".avi", "application/x-troff-msvideo");
setSuffix(".movie", "video/x-sgi-movie");
setSuffix(".mv", "video/x-sgi-movie");
setSuffix(".mime", "message/rfc822");
}
/**
* A useful utility routine that tries to guess the content-type
* of an object based upon its extension.
*/
public static String guessContentTypeFromName(String fname) {
String ext = "";
int i = fname.lastIndexOf('#');
if (i != -1)
fname = fname.substring(0, i - 1);
i = fname.lastIndexOf('.');
i = Math.max(i, fname.lastIndexOf('/'));
i = Math.max(i, fname.lastIndexOf('?'));
if (i != -1 && fname.charAt(i) == '.') {
ext = fname.substring(i).toLowerCase();
}
return (String) extension_map.get(ext);
}
public static MimeType getMimeType(String filename) {
try {
return new MimeType(guessContentTypeFromName(filename));
} catch (MimeTypeFormatException ex) {
return null;
}
}
}