Merge remote-tracking branch 'lachlan/jetty-9.4.x-1027-Multipart' into jetty-9.4.x-1027-Multipart

This commit is contained in:
Greg Wilkins 2018-03-27 10:29:05 +11:00
commit 585f03464b
6 changed files with 538 additions and 502 deletions

View File

@ -47,22 +47,21 @@ import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.ReadLineInputStream;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* MultiPartInputStream
*
* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
*
* @see https://tools.ietf.org/html/rfc7578
*/
public class MultiPartInputStreamParser
public class MultiPartFormInputStream
{
private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
private final int _bufferSize = 16*1024;
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class);
private final int _bufferSize = 16 * 1024;
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
protected InputStream _in;
protected MultipartConfigElement _config;
@ -74,8 +73,6 @@ public class MultiPartInputStreamParser
protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames;
public class MultiPart implements Part
{
protected String _name;
@ -88,8 +85,7 @@ public class MultiPartInputStreamParser
protected long _size = 0;
protected boolean _temporary = true;
public MultiPart (String name, String filename)
throws IOException
public MultiPart(String name, String filename) throws IOException
{
_name = name;
_filename = filename;
@ -100,72 +96,69 @@ public class MultiPartInputStreamParser
{
return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,t=%b,f=%s}",_name,_filename,_contentType,_size,_temporary,_file);
}
protected void setContentType (String contentType)
protected void setContentType(String contentType)
{
_contentType = contentType;
}
protected void open()
throws IOException
protected void open() throws 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.
// 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 (isWriteFilesWithFilenames() && _filename != null && _filename.trim().length() > 0)
{
createFile();
}
else
{
//Write to a buffer in memory until we discover we've exceed the
//MultipartConfig fileSizeThreshold
_out = _bout= new ByteArrayOutputStream2();
// Write to a buffer in memory until we discover we've exceed the
// MultipartConfig fileSizeThreshold
_out = _bout = new ByteArrayOutputStream2();
}
}
protected void close()
throws IOException
protected void close() throws IOException
{
_out.close();
}
protected void write (int b)
throws IOException
protected void write(int b) throws IOException
{
if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize())
throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold()
&& _file == null)
createFile();
_out.write(b);
_size ++;
_size++;
}
protected void write (byte[] bytes, int offset, int length)
throws IOException
protected void write(byte[] bytes, int offset, int length) throws IOException
{
if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize())
throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0
&& _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null)
createFile();
_out.write(bytes, offset, length);
_out.write(bytes,offset,length);
_size += length;
}
protected void createFile ()
throws IOException
protected void createFile() throws IOException
{
/* Some statics just to make the code below easier to understand
* This get optimized away during the compile anyway */
/*
* Some statics just to make the code below easier to understand This get optimized away during the compile anyway
*/
final boolean USER = true;
final boolean WORLD = false;
_file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir);
_file = File.createTempFile("MultiPart","",MultiPartFormInputStream.this._tmpDir);
_file.setReadable(false,WORLD); // (reset) disable it for everyone first
_file.setReadable(true,USER); // enable for user only
@ -176,7 +169,7 @@ public class MultiPartInputStreamParser
if (_size > 0 && _out != null)
{
//already written some bytes, so need to copy them into the file
// already written some bytes, so need to copy them into the file
_out.flush();
_bout.writeTo(bos);
_out.close();
@ -185,8 +178,6 @@ public class MultiPartInputStreamParser
_out = bos;
}
protected void setHeaders(MultiMap<String> headers)
{
_headers = headers;
@ -206,10 +197,10 @@ public class MultiPartInputStreamParser
*/
@Override
public String getHeader(String name)
{
{
if (name == null)
return null;
return _headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
return _headers.getValue(name.toLowerCase(Locale.ENGLISH),0);
}
/**
@ -227,7 +218,7 @@ public class MultiPartInputStreamParser
@Override
public Collection<String> getHeaders(String name)
{
return _headers.getValues(name);
return _headers.getValues(name);
}
/**
@ -236,19 +227,18 @@ public class MultiPartInputStreamParser
@Override
public InputStream getInputStream() throws IOException
{
if (_file != null)
{
//written to a file, whether temporary or not
return new BufferedInputStream (new FileInputStream(_file));
}
else
{
//part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
}
if (_file != null)
{
// written to a file, whether temporary or not
return new BufferedInputStream(new FileInputStream(_file));
}
else
{
// part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
}
}
/**
* @see javax.servlet.http.Part#getSubmittedFileName()
*/
@ -260,7 +250,7 @@ public class MultiPartInputStreamParser
public byte[] getBytes()
{
if (_bout!=null)
if (_bout != null)
return _bout.toByteArray();
return null;
}
@ -271,7 +261,7 @@ public class MultiPartInputStreamParser
@Override
public String getName()
{
return _name;
return _name;
}
/**
@ -293,8 +283,8 @@ public class MultiPartInputStreamParser
{
_temporary = false;
//part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File (_tmpDir, fileName);
// part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File(_tmpDir,fileName);
BufferedOutputStream bos = null;
try
@ -312,19 +302,19 @@ public class MultiPartInputStreamParser
}
else
{
//the part data is already written to a temporary file, just rename it
// the part data is already written to a temporary file, just rename it
_temporary = false;
Path src = _file.toPath();
Path target = src.resolveSibling(fileName);
Files.move(src, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(src,target,StandardCopyOption.REPLACE_EXISTING);
_file = target.toFile();
}
}
/**
* Remove the file, whether or not Part.write() was called on it
* (ie no longer temporary)
* Remove the file, whether or not Part.write() was called on it (ie no longer temporary)
*
* @see javax.servlet.http.Part#delete()
*/
@Override
@ -337,7 +327,8 @@ public class MultiPartInputStreamParser
/**
* Only remove tmp files.
*
* @throws IOException if unable to delete the file
* @throws IOException
* if unable to delete the file
*/
public void cleanUp() throws IOException
{
@ -345,47 +336,48 @@ public class MultiPartInputStreamParser
_file.delete();
}
/**
* Get the file
*
* @return the file, if any, the data has been written to.
*/
public File getFile ()
public File getFile()
{
return _file;
}
/**
* Get the filename from the content-disposition.
*
* @return null or the filename
*/
public String getContentDispositionFilename ()
public String getContentDispositionFilename()
{
return _filename;
}
}
/**
* @param in Request input stream
* @param contentType Content-Type header
* @param config MultipartConfigElement
* @param contextTmpDir javax.servlet.context.tempdir
* @param in
* Request input stream
* @param contentType
* Content-Type header
* @param config
* MultipartConfigElement
* @param contextTmpDir
* javax.servlet.context.tempdir
*/
public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
{
_contentType = contentType;
_config = config;
_contextTmpDir = contextTmpDir;
if (_contextTmpDir == null)
_contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
_contextTmpDir = new File(System.getProperty("java.io.tmpdir"));
if (_config == null)
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
if (in instanceof ServletInputStream)
{
if (((ServletInputStream)in).isFinished())
@ -394,11 +386,12 @@ public class MultiPartInputStreamParser
return;
}
}
_in = new ReadLineInputStream(in);
_in = new BufferedInputStream(in);
}
/**
* Get the already parsed parts.
*
* @return the parts that were parsed
*/
public Collection<Part> getParsedParts()
@ -408,9 +401,9 @@ public class MultiPartInputStreamParser
Collection<List<Part>> values = _parts.values();
List<Part> parts = new ArrayList<>();
for (List<Part> o: values)
for (List<Part> o : values)
{
List<Part> asList = LazyList.getList(o, false);
List<Part> asList = LazyList.getList(o,false);
parts.addAll(asList);
}
return parts;
@ -419,20 +412,20 @@ public class MultiPartInputStreamParser
/**
* Delete any tmp storage for parts, and clear out the parts list.
*
* @throws MultiException if unable to delete the parts
* @throws MultiException
* if unable to delete the parts
*/
public void deleteParts ()
throws MultiException
public void deleteParts() throws MultiException
{
Collection<Part> parts = getParsedParts();
MultiException err = new MultiException();
for (Part p:parts)
for (Part p : parts)
{
try
{
((MultiPartInputStreamParser.MultiPart)p).cleanUp();
((MultiPart)p).cleanUp();
}
catch(Exception e)
catch (Exception e)
{
err.add(e);
}
@ -442,53 +435,51 @@ public class MultiPartInputStreamParser
err.ifExceptionThrowMulti();
}
/**
* Parse, if necessary, the multipart data and return the list of Parts.
*
* @return the parts
* @throws IOException if unable to get the parts
* @throws IOException
* if unable to get the parts
*/
public Collection<Part> getParts()
throws IOException
public Collection<Part> getParts() throws IOException
{
parse();
throwIfError();
Collection<List<Part>> values = _parts.values();
List<Part> parts = new ArrayList<>();
for (List<Part> o: values)
for (List<Part> o : values)
{
List<Part> asList = LazyList.getList(o, false);
List<Part> asList = LazyList.getList(o,false);
parts.addAll(asList);
}
return parts;
}
/**
* Get the named Part.
*
* @param name the part name
* @param name
* the part name
* @return the parts
* @throws IOException if unable to get the part
* @throws IOException
* if unable to get the part
*/
public Part getPart(String name)
throws IOException
public Part getPart(String name) throws IOException
{
parse();
throwIfError();
return _parts.getValue(name, 0);
throwIfError();
return _parts.getValue(name,0);
}
/**
* Throws an exception if one has been latched.
*
* @throws IOException the exception (if present)
* @throws IOException
* the exception (if present)
*/
protected void throwIfError ()
throws IOException
protected void throwIfError() throws IOException
{
if (_err != null)
{
@ -505,34 +496,33 @@ public class MultiPartInputStreamParser
* Parse, if necessary, the multipart stream.
*
*/
protected void parse ()
protected void parse()
{
//have we already parsed the input?
// have we already parsed the input?
if (_parts != null || _err != null)
return;
try
{
//initialize
// initialize
_parts = new MultiMap<>();
//if its not a multipart request, don't parse it
// 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
// sort out the location to which to write the files
if (_config.getLocation() == null)
_tmpDir = _contextTmpDir;
else if ("".equals(_config.getLocation()))
_tmpDir = _contextTmpDir;
else
{
File f = new File (_config.getLocation());
File f = new File(_config.getLocation());
if (f.isAbsolute())
_tmpDir = f;
else
_tmpDir = new File (_contextTmpDir, _config.getLocation());
_tmpDir = new File(_contextTmpDir,_config.getLocation());
}
if (!_tmpDir.exists())
@ -542,72 +532,74 @@ public class MultiPartInputStreamParser
int bstart = _contentType.indexOf("boundary=");
if (bstart >= 0)
{
int bend = _contentType.indexOf(";", bstart);
bend = (bend < 0? _contentType.length(): bend);
int bend = _contentType.indexOf(";",bstart);
bend = (bend < 0?_contentType.length():bend);
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
}
Handler handler = new Handler();
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
// Create a buffer to store data from stream //
byte[] data = new byte[_bufferSize];
int len=0;
int len = 0;
/* keep running total of size of bytes read from input
* and throw an exception if exceeds MultipartConfigElement._maxRequestSize */
long total = 0;
/*
* keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
*/
long total = 0;
while(true)
while (true)
{
len = _in.read(data);
if(len > 0)
if (len > 0)
{
total+=len;
if(_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
total += len;
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
{
_err = new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
_err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");
return;
}
ByteBuffer buffer = BufferUtil.toBuffer(data);
buffer.limit(len);
parser.parse(buffer, false);
}
else if (len == -1)
if (parser.parse(buffer,false))
break;
if(buffer.hasRemaining())
throw new IllegalStateException("Buffer did not fully consume");
}
else if (len == -1)
{
parser.parse(BufferUtil.EMPTY_BUFFER, true);
parser.parse(BufferUtil.EMPTY_BUFFER,true);
break;
}
}
//check for exceptions
if(_err != null)
// check for exceptions
if (_err != null)
{
return;
}
//check we read to the end of the message
if(parser.getState() != MultiPartParser.State.END)
// check we read to the end of the message
if (parser.getState() != MultiPartParser.State.END)
{
if(parser.getState() == MultiPartParser.State.PREAMBLE)
if (parser.getState() == MultiPartParser.State.PREAMBLE)
_err = new IOException("Missing initial multi part boundary");
else
_err = new IOException("Incomplete Multipart");
}
if(LOG.isDebugEnabled())
if (LOG.isDebugEnabled())
{
LOG.debug("Parsing Complete {} err={}",parser,_err);
}
}
catch (Throwable e)
{
@ -616,98 +608,83 @@ public class MultiPartInputStreamParser
}
}
class Handler implements MultiPartParser.Handler
{
private MultiPart _part=null;
private String contentDisposition=null;
private String contentType=null;
private MultiPart _part = null;
private String contentDisposition = null;
private String contentType = null;
private MultiMap<String> headers = new MultiMap<>();
@Override
public boolean messageComplete() { return true; }
public boolean messageComplete()
{
return true;
}
@Override
public void parsedField(String key, String value)
{
// Add to headers and mark if one of these fields. //
headers.put(key.toLowerCase(Locale.ENGLISH), value);
headers.put(key.toLowerCase(Locale.ENGLISH),value);
if (key.equalsIgnoreCase("content-disposition"))
contentDisposition=value;
contentDisposition = value;
else if (key.equalsIgnoreCase("content-type"))
contentType = value;
contentType = value;
}
@Override
public boolean headerComplete() {
try {
public boolean headerComplete()
{
try
{
// Extract content-disposition
boolean form_data=false;
if(contentDisposition==null)
boolean form_data = false;
if (contentDisposition == null)
{
throw new IOException("Missing content-disposition");
}
QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
String name=null;
String filename=null;
while(tok.hasMoreTokens())
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true);
String name = null;
String filename = null;
while (tok.hasMoreTokens())
{
String t=tok.nextToken().trim();
String tl=t.toLowerCase(Locale.ENGLISH);
if(t.startsWith("form-data"))
form_data=true;
else if(tl.startsWith("name="))
name=value(t);
else if(tl.startsWith("filename="))
filename=filenameValue(t);
String t = tok.nextToken().trim();
String tl = t.toLowerCase(Locale.ENGLISH);
if (t.startsWith("form-data"))
form_data = true;
else if (tl.startsWith("name="))
name = value(t);
else if (tl.startsWith("filename="))
filename = filenameValue(t);
}
// Check disposition
if(!form_data)
if (!form_data)
{
return false;
}
//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)
// 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)
{
return false;
}
//create the new part
_part = new MultiPart(name, filename);
// create the new part
_part = new MultiPart(name,filename);
_part.setHeaders(headers);
_part.setContentType(contentType);
_parts.add(name, _part);
}
catch (Exception e)
{
_err = e;
return true;
}
return false;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
if (BufferUtil.hasContent(buffer))
{
_parts.add(name,_part);
try
{
//write the content data to the part
_part.open();
_part.write(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
_part.close();
}
catch (IOException e)
{
@ -715,13 +692,48 @@ public class MultiPartInputStreamParser
return true;
}
}
if (last)
reset();
catch (Exception e)
{
_err = e;
return true;
}
return false;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
if (BufferUtil.hasContent(buffer))
{
try
{
_part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
}
catch (IOException e)
{
_err = e;
return true;
}
}
if (last)
{
try
{
_part.close();
}
catch (IOException e)
{
_err = e;
return true;
}
reset();
}
return false;
}
public void reset()
{
_part = null;
@ -729,21 +741,27 @@ public class MultiPartInputStreamParser
contentType = null;
headers = new MultiMap<>();
}
@Override
public void earlyEOF()
{
if (LOG.isDebugEnabled())
LOG.debug("Early EOF {}",MultiPartFormInputStream.this);
}
}
public void setDeleteOnExit(boolean deleteOnExit)
{
_deleteOnExit = deleteOnExit;
}
public void setWriteFilesWithFilenames (boolean writeFilesWithFilenames)
public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames)
{
_writeFilesWithFilenames = writeFilesWithFilenames;
}
public boolean isWriteFilesWithFilenames ()
public boolean isWriteFilesWithFilenames()
{
return _writeFilesWithFilenames;
}
@ -753,42 +771,39 @@ public class MultiPartInputStreamParser
return _deleteOnExit;
}
/* ------------------------------------------------------------ */
private String value(String nameEqualsValue)
{
int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx+1).trim();
String value = nameEqualsValue.substring(idx + 1).trim();
return QuotedStringTokenizer.unquoteOnly(value);
}
/* ------------------------------------------------------------ */
private String filenameValue(String nameEqualsValue)
{
int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx+1).trim();
String value = nameEqualsValue.substring(idx + 1).trim();
if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
{
//incorrectly escaped IE filenames that have the whole path
//we just strip any leading & trailing quotes and leave it as is
char first=value.charAt(0);
if (first=='"' || first=='\'')
value=value.substring(1);
char last=value.charAt(value.length()-1);
if (last=='"' || last=='\'')
value = value.substring(0,value.length()-1);
// incorrectly escaped IE filenames that have the whole path
// we just strip any leading & trailing quotes and leave it as is
char first = value.charAt(0);
if (first == '"' || first == '\'')
value = value.substring(1);
char last = value.charAt(value.length() - 1);
if (last == '"' || last == '\'')
value = value.substring(0,value.length() - 1);
return value;
}
else
//unquote the string, but allow any backslashes that don't
//form a valid escape sequence to remain as many browsers
//even on *nix systems will not escape a filename containing
//backslashes
return QuotedStringTokenizer.unquoteOnly(value, true);
// unquote the string, but allow any backslashes that don't
// form a valid escape sequence to remain as many browsers
// even on *nix systems will not escape a filename containing
// backslashes
return QuotedStringTokenizer.unquoteOnly(value,true);
}
}

View File

@ -29,7 +29,6 @@ import org.eclipse.jetty.util.SearchPattern;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/*
* RFC2046 and RFC7578
@ -114,42 +113,42 @@ public class MultiPartParser
{
public static final Logger LOG = Log.getLogger(MultiPartParser.class);
static final byte COLON= (byte)':';
static final byte TAB= 0x09;
static final byte LINE_FEED= 0x0A;
static final byte CARRIAGE_RETURN= 0x0D;
static final byte SPACE= 0x20;
static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
static final byte SEMI_COLON= (byte)';';
static final byte COLON = (byte)':';
static final byte TAB = 0x09;
static final byte LINE_FEED = 0x0A;
static final byte CARRIAGE_RETURN = 0x0D;
static final byte SPACE = 0x20;
static final byte[] CRLF =
{ CARRIAGE_RETURN, LINE_FEED };
static final byte SEMI_COLON = (byte)';';
// States
public enum FieldState
{
FIELD,
IN_NAME,
AFTER_NAME,
AFTER_NAME,
VALUE,
IN_VALUE
}
// States
public enum State
{
PREAMBLE,
DELIMITER,
DELIMITER_PADDING,
DELIMITER_CLOSE,
DELIMITER_PADDING,
DELIMITER_CLOSE,
BODY_PART,
FIRST_OCTETS,
OCTETS,
EPILOGUE,
FIRST_OCTETS,
OCTETS,
EPILOGUE,
END
}
private final static EnumSet<State> __delimiterStates = EnumSet.of(State.DELIMITER,State.DELIMITER_CLOSE,State.DELIMITER_PADDING);
private final boolean DEBUG=LOG.isDebugEnabled();
private final boolean DEBUG = LOG.isDebugEnabled();
private final Handler _handler;
private final SearchPattern _delimiterSearch;
@ -162,7 +161,7 @@ public class MultiPartParser
private boolean _cr;
private ByteBuffer _patternBuffer;
private final StringBuilder _string=new StringBuilder();
private final StringBuilder _string = new StringBuilder();
private int _length;
private int _totalHeaderLineLength = -1;
@ -173,7 +172,7 @@ public class MultiPartParser
{
_handler = handler;
String delimiter = "\r\n--"+boundary;
String delimiter = "\r\n--" + boundary;
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
_delimiterSearch = SearchPattern.compile(_patternBuffer.array());
}
@ -184,14 +183,13 @@ public class MultiPartParser
_fieldState = FieldState.FIELD;
_partialBoundary = 2; // No CRLF if no preamble
}
/* ------------------------------------------------------------------------------- */
public Handler getHandler()
{
return _handler;
}
/* ------------------------------------------------------------------------------- */
public State getState()
{
@ -205,54 +203,57 @@ public class MultiPartParser
}
/* ------------------------------------------------------------------------------- */
enum CharState { ILLEGAL, CR, LF, LEGAL }
enum CharState
{
ILLEGAL, CR, LF, LEGAL
}
private final static CharState[] __charState;
static
{
// token = 1*tchar
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
// ; any VCHAR, except delimiters
// quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
// qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
// obs-text = %x80-FF
// comment = "(" *( ctext / quoted-pair / comment ) ")"
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
// token = 1*tchar
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
// ; any VCHAR, except delimiters
// quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
// qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
// obs-text = %x80-FF
// comment = "(" *( ctext / quoted-pair / comment ) ")"
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
__charState=new CharState[256];
__charState = new CharState[256];
Arrays.fill(__charState,CharState.ILLEGAL);
__charState[LINE_FEED]=CharState.LF;
__charState[CARRIAGE_RETURN]=CharState.CR;
__charState[TAB]=CharState.LEGAL;
__charState[SPACE]=CharState.LEGAL;
__charState[LINE_FEED] = CharState.LF;
__charState[CARRIAGE_RETURN] = CharState.CR;
__charState[TAB] = CharState.LEGAL;
__charState[SPACE] = CharState.LEGAL;
__charState['!']=CharState.LEGAL;
__charState['#']=CharState.LEGAL;
__charState['$']=CharState.LEGAL;
__charState['%']=CharState.LEGAL;
__charState['&']=CharState.LEGAL;
__charState['\'']=CharState.LEGAL;
__charState['*']=CharState.LEGAL;
__charState['+']=CharState.LEGAL;
__charState['-']=CharState.LEGAL;
__charState['.']=CharState.LEGAL;
__charState['^']=CharState.LEGAL;
__charState['_']=CharState.LEGAL;
__charState['`']=CharState.LEGAL;
__charState['|']=CharState.LEGAL;
__charState['~']=CharState.LEGAL;
__charState['!'] = CharState.LEGAL;
__charState['#'] = CharState.LEGAL;
__charState['$'] = CharState.LEGAL;
__charState['%'] = CharState.LEGAL;
__charState['&'] = CharState.LEGAL;
__charState['\''] = CharState.LEGAL;
__charState['*'] = CharState.LEGAL;
__charState['+'] = CharState.LEGAL;
__charState['-'] = CharState.LEGAL;
__charState['.'] = CharState.LEGAL;
__charState['^'] = CharState.LEGAL;
__charState['_'] = CharState.LEGAL;
__charState['`'] = CharState.LEGAL;
__charState['|'] = CharState.LEGAL;
__charState['~'] = CharState.LEGAL;
__charState['"']=CharState.LEGAL;
__charState['"'] = CharState.LEGAL;
__charState['\\']=CharState.LEGAL;
__charState['(']=CharState.LEGAL;
__charState[')']=CharState.LEGAL;
Arrays.fill(__charState,0x21,0x27+1,CharState.LEGAL);
Arrays.fill(__charState,0x2A,0x5B+1,CharState.LEGAL);
Arrays.fill(__charState,0x5D,0x7E+1,CharState.LEGAL);
Arrays.fill(__charState,0x80,0xFF+1,CharState.LEGAL);
__charState['\\'] = CharState.LEGAL;
__charState['('] = CharState.LEGAL;
__charState[')'] = CharState.LEGAL;
Arrays.fill(__charState,0x21,0x27 + 1,CharState.LEGAL);
Arrays.fill(__charState,0x2A,0x5B + 1,CharState.LEGAL);
Arrays.fill(__charState,0x5D,0x7E + 1,CharState.LEGAL);
Arrays.fill(__charState,0x80,0xFF + 1,CharState.LEGAL);
}
@ -261,7 +262,7 @@ public class MultiPartParser
{
return BufferUtil.hasContent(buffer);
}
/* ------------------------------------------------------------------------------- */
private byte getNextByte(ByteBuffer buffer)
{
@ -269,17 +270,17 @@ public class MultiPartParser
byte ch = buffer.get();
CharState s = __charState[0xff & ch];
switch(s)
switch (s)
{
case LF:
_cr=false;
_cr = false;
return ch;
case CR:
if (_cr)
throw new BadMessageException("Bad EOL");
_cr=true;
_cr = true;
if (buffer.hasRemaining())
return getNextByte(buffer);
@ -299,75 +300,75 @@ public class MultiPartParser
}
}
/* ------------------------------------------------------------------------------- */
private void setString(String s)
{
_string.setLength(0);
_string.append(s);
_length=s.length();
_length = s.length();
}
/* ------------------------------------------------------------------------------- */
private String takeString()
{
_string.setLength(_length);
String s =_string.toString();
String s = _string.toString();
_string.setLength(0);
_length=-1;
_length = -1;
return s;
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
* @param buffer the buffer to parse
*
* @param buffer
* the buffer to parse
* @return True if an {@link RequestHandler} method was called and it returned true;
*/
public boolean parse(ByteBuffer buffer, boolean last)
{
boolean handle = false;
while(handle==false && BufferUtil.hasContent(buffer))
while (handle == false && BufferUtil.hasContent(buffer))
{
switch(_state)
switch (_state)
{
case PREAMBLE:
parsePreamble(buffer);
continue;
case DELIMITER:
case DELIMITER_PADDING:
case DELIMITER_CLOSE:
parseDelimiter(buffer);
continue;
case BODY_PART:
handle = parseMimePartHeaders(buffer);
break;
case FIRST_OCTETS:
case OCTETS:
handle = parseOctetContent(buffer);
break;
case EPILOGUE:
BufferUtil.clear(buffer);
break;
case END:
handle = true;
break;
default:
throw new IllegalStateException();
}
}
if(last && BufferUtil.isEmpty(buffer))
if (last && BufferUtil.isEmpty(buffer))
{
if(_state == State.EPILOGUE)
if (_state == State.EPILOGUE)
{
_state = State.END;
return _handler.messageComplete();
@ -378,21 +379,21 @@ public class MultiPartParser
return true;
}
}
return handle;
}
/* ------------------------------------------------------------------------------- */
private void parsePreamble(ByteBuffer buffer)
{
if (_partialBoundary>0)
if (_partialBoundary > 0)
{
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary);
if (partial>0)
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary);
if (partial > 0)
{
if (partial==_delimiterSearch.getLength())
if (partial == _delimiterSearch.getLength())
{
buffer.position(buffer.position()+partial-_partialBoundary);
buffer.position(buffer.position() + partial - _partialBoundary);
_partialBoundary = 0;
setState(State.DELIMITER);
return;
@ -402,61 +403,61 @@ public class MultiPartParser
BufferUtil.clear(buffer);
return;
}
_partialBoundary = 0;
}
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
if (delimiter>=0)
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (delimiter >= 0)
{
buffer.position(delimiter-buffer.arrayOffset()+_delimiterSearch.getLength());
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER);
return;
}
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
BufferUtil.clear(buffer);
return;
}
/* ------------------------------------------------------------------------------- */
private void parseDelimiter(ByteBuffer buffer)
{
{
while (__delimiterStates.contains(_state) && hasNextByte(buffer))
{
byte b=getNextByte(buffer);
if (b==0)
byte b = getNextByte(buffer);
if (b == 0)
return;
if (b=='\n')
if (b == '\n')
{
setState(State.BODY_PART);
_handler.startPart();
return;
}
}
switch(_state)
switch (_state)
{
case DELIMITER:
if (b=='-')
if (b == '-')
setState(State.DELIMITER_CLOSE);
else
setState(State.DELIMITER_PADDING);
continue;
case DELIMITER_CLOSE:
if (b=='-')
if (b == '-')
{
setState(State.EPILOGUE);
return;
}
setState(State.DELIMITER_PADDING);
continue;
case DELIMITER_PADDING:
default:
continue;
continue;
}
}
}
@ -468,44 +469,43 @@ public class MultiPartParser
protected boolean parseMimePartHeaders(ByteBuffer buffer)
{
// Process headers
while (_state==State.BODY_PART && hasNextByte(buffer))
while (_state == State.BODY_PART && hasNextByte(buffer))
{
// process each character
byte b=getNextByte(buffer);
if (b==0)
byte b = getNextByte(buffer);
if (b == 0)
break;
if(b!=LINE_FEED)
_totalHeaderLineLength++;
if(_totalHeaderLineLength > _maxHeaderLineLength)
throw new IllegalStateException("Header Line Exceeded Max Length");
if (b != LINE_FEED)
_totalHeaderLineLength++;
if (_totalHeaderLineLength > _maxHeaderLineLength)
throw new IllegalStateException("Header Line Exceeded Max Length");
switch (_fieldState)
{
case FIELD:
switch(b)
switch (b)
{
case SPACE:
case TAB:
{
// Folded field value!
if (_fieldName==null)
if (_fieldName == null)
throw new IllegalStateException("First field folded");
if (_fieldValue==null)
if (_fieldValue == null)
{
_string.setLength(0);
_length=0;
_length = 0;
}
else
{
setString(_fieldValue);
_string.append(' ');
_length++;
_fieldValue=null;
_fieldValue = null;
}
setState(FieldState.VALUE);
break;
@ -530,100 +530,100 @@ public class MultiPartParser
setState(FieldState.IN_NAME);
_string.setLength(0);
_string.append((char)b);
_length=1;
_length = 1;
}
}
break;
case IN_NAME:
switch(b)
switch (b)
{
case COLON:
_fieldName=takeString();
_length=-1;
_fieldName = takeString();
_length = -1;
setState(FieldState.VALUE);
break;
case SPACE:
//Ignore trailing whitespaces
// Ignore trailing whitespaces
setState(FieldState.AFTER_NAME);
break;
default:
_string.append((char)b);
_length=_string.length();
_length = _string.length();
break;
}
break;
case AFTER_NAME:
switch(b)
switch (b)
{
case COLON:
_fieldName=takeString();
_length=-1;
_fieldName = takeString();
_length = -1;
setState(FieldState.VALUE);
break;
case LINE_FEED:
_fieldName=takeString();
_fieldName = takeString();
_string.setLength(0);
_fieldValue="";
_length=-1;
_fieldValue = "";
_length = -1;
break;
case SPACE:
break;
default:
throw new IllegalCharacterException(_state,b,buffer);
}
break;
case VALUE:
switch(b)
switch (b)
{
case LINE_FEED:
_string.setLength(0);
_fieldValue="";
_length=-1;
_fieldValue = "";
_length = -1;
setState(FieldState.FIELD);
break;
case SPACE:
case TAB:
break;
default:
_string.append((char)(0xff&b));
_length=_string.length();
_string.append((char)(0xff & b));
_length = _string.length();
setState(FieldState.IN_VALUE);
break;
}
break;
case IN_VALUE:
switch(b)
switch (b)
{
case SPACE:
_string.append((char)(0xff&b));
_string.append((char)(0xff & b));
break;
case LINE_FEED:
if (_length > 0)
{
_fieldValue=takeString();
_length=-1;
_totalHeaderLineLength=-1;
_fieldValue = takeString();
_length = -1;
_totalHeaderLineLength = -1;
}
setState(FieldState.FIELD);
break;
default:
_string.append((char)(0xff&b));
if (b>SPACE || b<0)
_length=_string.length();
_string.append((char)(0xff & b));
if (b > SPACE || b < 0)
_length = _string.length();
break;
}
break;
@ -635,95 +635,91 @@ public class MultiPartParser
}
return false;
}
/* ------------------------------------------------------------------------------- */
private void handleField()
{
if (_fieldName!=null && _fieldValue!=null)
if (_fieldName != null && _fieldValue != null)
_handler.parsedField(_fieldName,_fieldValue);
_fieldName = _fieldValue = null;
}
/* ------------------------------------------------------------------------------- */
protected boolean parseOctetContent(ByteBuffer buffer)
{
//Starts With
if (_partialBoundary>0)
// Starts With
if (_partialBoundary > 0)
{
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary);
if (partial>0)
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary);
if (partial > 0)
{
if (partial==_delimiterSearch.getLength())
if (partial == _delimiterSearch.getLength())
{
buffer.position(buffer.position() + _delimiterSearch.getLength() - _partialBoundary);
setState(State.DELIMITER);
_partialBoundary = 0;
return _handler.content(BufferUtil.EMPTY_BUFFER, true);
return _handler.content(BufferUtil.EMPTY_BUFFER,true);
}
_partialBoundary = partial;
BufferUtil.clear(buffer);
BufferUtil.clear(buffer);
return false;
}
else
{
//output up to _partialBoundary of the search pattern
// output up to _partialBoundary of the search pattern
ByteBuffer content = _patternBuffer.slice();
if (_state==State.FIRST_OCTETS)
if (_state == State.FIRST_OCTETS)
{
setState(State.OCTETS);
content.position(2);
}
content.limit(_partialBoundary);
_partialBoundary = 0;
if (_handler.content(content, false))
if (_handler.content(content,false))
return true;
}
}
// Contains
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
if (delimiter>=0)
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (delimiter >= 0)
{
ByteBuffer content = buffer.slice();
content.limit(delimiter - buffer.arrayOffset() - buffer.position());
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER);
return _handler.content(content, true);
return _handler.content(content,true);
}
// Ends With
_partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset()+buffer.position(), buffer.remaining());
if(_partialBoundary > 0)
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (_partialBoundary > 0)
{
ByteBuffer content = buffer.slice();
content.limit(content.limit() - _partialBoundary);
BufferUtil.clear(buffer);
return _handler.content(content, false);
return _handler.content(content,false);
}
// There is normal content with no delimiter
ByteBuffer content = buffer.slice();
BufferUtil.clear(buffer);
return _handler.content(content, false);
return _handler.content(content,false);
}
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
if (DEBUG)
LOG.debug("{} --> {}",_state,state);
_state=state;
_state = state;
}
/* ------------------------------------------------------------------------------- */
@ -731,47 +727,59 @@ public class MultiPartParser
{
if (DEBUG)
LOG.debug("{}:{} --> {}",_state,_fieldState,state);
_fieldState=state;
_fieldState = state;
}
/* ------------------------------------------------------------------------------- */
@Override
public String toString()
{
return String.format("%s{s=%s}",
getClass().getSimpleName(),
_state);
return String.format("%s{s=%s}",getClass().getSimpleName(),_state);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* Event Handler interface
* These methods return true if the caller should process the events
* so far received (eg return from parseNext and call HttpChannel.handle).
* If multiple callbacks are called in sequence (eg
* headerComplete then messageComplete) from the same point in the parsing
* then it is sufficient for the caller to process the events only once.
/*
* Event Handler interface These methods return true if the caller should process the events so far received (eg return from parseNext and call
* HttpChannel.handle). If multiple callbacks are called in sequence (eg headerComplete then messageComplete) from the same point in the parsing then it is
* sufficient for the caller to process the events only once.
*/
public interface Handler
{
public default void startPart() {}
public default void parsedField(String name, String value) {}
public default boolean headerComplete() {return false;}
public default boolean content(ByteBuffer item, boolean last) {return false;}
public default boolean messageComplete() {return false;}
public default void startPart()
{
}
public default void earlyEOF() {}
public default void parsedField(String name, String value)
{
}
public default boolean headerComplete()
{
return false;
}
public default boolean content(ByteBuffer item, boolean last)
{
return false;
}
public default boolean messageComplete()
{
return false;
}
public default void earlyEOF()
{
}
}
/* ------------------------------------------------------------------------------- */
@SuppressWarnings("serial")
private static class IllegalCharacterException extends IllegalArgumentException
{
private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
private IllegalCharacterException(State state, byte ch, ByteBuffer buffer)
{
super(String.format("Illegal character 0x%X",ch));
// Bug #460642 - don't reveal buffers to end user

View File

@ -44,7 +44,7 @@ import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartInputStreamParser.MultiPart;
import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
@ -55,7 +55,7 @@ import org.junit.Test;
*
*
*/
public class MultiPartInputStreamTest
public class MultiPartFormInputStreamTest
{
private static final String FILENAME = "stuff.txt";
protected String _contentType = "multipart/form-data, boundary=AaB03x";
@ -63,7 +63,7 @@ public class MultiPartInputStreamTest
protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
protected File _tmpDir = new File(_dirname);
public MultiPartInputStreamTest ()
public MultiPartFormInputStreamTest ()
{
_tmpDir.deleteOnExit();
}
@ -82,7 +82,7 @@ public class MultiPartInputStreamTest
+ "\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary,
config,
_tmpDir);
@ -116,7 +116,7 @@ public class MultiPartInputStreamTest
"--" + boundary + "--" + delimiter;
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary,
config,
_tmpDir);
@ -139,7 +139,7 @@ public class MultiPartInputStreamTest
"--" + boundary + "--" + delimiter;
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary,
config,
_tmpDir);
@ -178,7 +178,7 @@ public class MultiPartInputStreamTest
"----\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data",
config,
_tmpDir);
@ -213,7 +213,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
"Content-type: text/plain",
config,
_tmpDir);
@ -228,7 +228,7 @@ public class MultiPartInputStreamTest
String body = "";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
@ -277,7 +277,7 @@ public class MultiPartInputStreamTest
};
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(is,
MultiPartFormInputStream mpis = new MultiPartFormInputStream(is,
_contentType,
config,
_tmpDir);
@ -296,7 +296,7 @@ public class MultiPartInputStreamTest
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()),
_contentType,
config,
_tmpDir);
@ -319,7 +319,7 @@ public class MultiPartInputStreamTest
String whitespace = " ";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()),
_contentType,
config,
_tmpDir);
@ -353,7 +353,7 @@ public class MultiPartInputStreamTest
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
@ -394,7 +394,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
@ -422,7 +422,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
@ -436,7 +436,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
@ -459,7 +459,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
@ -494,7 +494,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
@ -516,7 +516,7 @@ public class MultiPartInputStreamTest
throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
@ -527,9 +527,8 @@ public class MultiPartInputStreamTest
parts = mpis.getParts(); //caused parsing
fail("stuff.txt should have been larger than maxFileSize");
}
catch (IllegalStateException e)
catch (Throwable e)
{
e.printStackTrace();
assertTrue(e.getMessage().startsWith("Multipart Mime part"));
}
@ -551,7 +550,7 @@ public class MultiPartInputStreamTest
public void testPartFileNotDeleted () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
@ -559,7 +558,7 @@ public class MultiPartInputStreamTest
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile();
File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
part.write("tptfd.txt");
File tptfd = new File (_dirname+File.separator+"tptfd.txt");
@ -574,7 +573,7 @@ public class MultiPartInputStreamTest
public void testPartTmpFileDeletion () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
@ -582,7 +581,7 @@ public class MultiPartInputStreamTest
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile();
File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat (stuff.exists(), is(true));
part.cleanUp();
@ -604,7 +603,7 @@ public class MultiPartInputStreamTest
"\r\n--AaB03x--\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
@ -641,7 +640,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
@ -688,7 +687,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
@ -729,7 +728,7 @@ public class MultiPartInputStreamTest
}
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(baos.toByteArray()),
_contentType,
config,
_tmpDir);
@ -758,7 +757,7 @@ public class MultiPartInputStreamTest
"--TheBoundary--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
contentType,
config,
_tmpDir);
@ -780,14 +779,14 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
}
@Test
@ -802,14 +801,14 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
@Test
@ -823,14 +822,14 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
public void testMulti ()
@ -862,7 +861,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
//all default values for multipartconfig, ie file size threshold 0
MultipartConfigElement config = new MultipartConfigElement(_dirname);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(s.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(s.getBytes()),
_contentType,
config,
_tmpDir);
@ -871,11 +870,11 @@ public class MultiPartInputStreamTest
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); //has a filename, should be written to a file
File f = ((MultiPartInputStreamParser.MultiPart)field1).getFile();
File f = ((MultiPartFormInputStream.MultiPart)field1).getFile();
assertThat(f,notNullValue()); // longer than 100 bytes, should already be a tmp file
Part stuff = mpis.getPart("stuff");
f = ((MultiPartInputStreamParser.MultiPart)stuff).getFile(); //should only be in memory, no filename
f = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); //should only be in memory, no filename
assertThat(f, nullValue());
}
@ -883,7 +882,7 @@ public class MultiPartInputStreamTest
private void testMulti(String filename) throws IOException, ServletException, InterruptedException
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
_contentType,
config,
_tmpDir);
@ -899,9 +898,9 @@ public class MultiPartInputStreamTest
assertEquals("Joe Blow", new String(os.toByteArray()));
assertEquals(8, field1.getSize());
assertNotNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//in internal buffer
assertNotNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//in internal buffer
field1.write("field1.txt");
assertNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//no longer in internal buffer
assertNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//no longer in internal buffer
File f = new File (_dirname+File.separator+"field1.txt");
assertTrue(f.exists());
field1.write("another_field1.txt"); //write after having already written
@ -921,7 +920,7 @@ public class MultiPartInputStreamTest
assertThat(stuff.getHeaderNames().size(),is(2));
assertThat(stuff.getSize(),is(51L));
File tmpfile = ((MultiPartInputStreamParser.MultiPart)stuff).getFile();
File tmpfile = ((MultiPartFormInputStream.MultiPart)stuff).getFile();
assertThat(tmpfile,notNullValue()); // longer than 50 bytes, should already be a tmp file
assertThat(stuff.getBytes(),nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(),is(true));
@ -958,7 +957,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(sameNames.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(sameNames.getBytes()),
_contentType,
config,
_tmpDir);
@ -997,7 +996,7 @@ public class MultiPartInputStreamTest
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
_contentType,
config,
_tmpDir);
@ -1041,7 +1040,7 @@ public class MultiPartInputStreamTest
"truth=3Dbeauty" + "\r\n"+
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
_contentType,
config,
_tmpDir);

View File

@ -55,7 +55,15 @@ import org.eclipse.jetty.util.log.Logger;
* MultiPartInputStream
*
* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
*
* Non Compliance warnings are documented by the method {@link #getNonComplianceWarnings()}
*
* @deprecated Replaced by {@link org.eclipse.jetty.http#MultiPartFormInputStream}
* The code for MultiPartInputStream is slower than its replacement MultiPartFormInputStream. However
* this class accepts formats non compliant the RFC that the new MultiPartFormInputStream does not accept.
*
*/
@Deprecated
public class MultiPartInputStreamParser
{
private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
@ -71,7 +79,7 @@ public class MultiPartInputStreamParser
protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames;
EnumSet<NonCompliance> nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class);
private EnumSet<NonCompliance> nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class);
public enum NonCompliance
{
CR_TERMINATION,
@ -80,15 +88,12 @@ public class MultiPartInputStreamParser
BASE64_TRANSFER_ENCODING,
QUOTED_PRINTABLE_TRANSFER_ENCODING
}
/**
* @return an EnumSet of non compliances with the RFC that were accepted by this parser
*/
public EnumSet<NonCompliance> getNonComplianceWarnings()
{
EnumSet<Termination> term = ((ReadLineInputStream)_in).getLineTerminations();
if(term.contains(Termination.CR))
nonComplianceWarnings.add(NonCompliance.CR_TERMINATION);
if(term.contains(Termination.LF))
nonComplianceWarnings.add(NonCompliance.LF_TERMINATION);
{
return nonComplianceWarnings;
}
@ -838,6 +843,13 @@ public class MultiPartInputStreamParser
{
while(line!=null)
line=((ReadLineInputStream)_in).readLine();
EnumSet<Termination> term = ((ReadLineInputStream)_in).getLineTerminations();
if(term.contains(Termination.CR))
nonComplianceWarnings.add(NonCompliance.CR_TERMINATION);
if(term.contains(Termination.LF))
nonComplianceWarnings.add(NonCompliance.LF_TERMINATION);
}
else
throw new IOException("Incomplete parts");
@ -846,6 +858,7 @@ public class MultiPartInputStreamParser
{
_err = e;
}
}
public void setDeleteOnExit(boolean deleteOnExit)

View File

@ -31,6 +31,7 @@ import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance;
*
* Read from an input stream, accepting CR/LF, LF or just CR.
*/
@Deprecated
public class ReadLineInputStream extends BufferedInputStream
{
boolean _seenCRLF;

View File

@ -47,7 +47,6 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.util.MultiPartInputStreamParser.MultiPart;
import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance;
import org.hamcrest.Matchers;
import org.junit.Test;
/**
@ -55,6 +54,7 @@ import org.junit.Test;
*
*
*/
@SuppressWarnings("deprecation")
public class MultiPartInputStreamTest
{
private static final String FILENAME = "stuff.txt";