Merge pull request #2476 from lachlan-roberts/jetty-9.4.x-1027-MultiPart-Cleanup

MultiPart Cleanup
This commit is contained in:
Greg Wilkins 2018-05-04 21:10:50 +10:00 committed by GitHub
commit c865aa1be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1268 additions and 1413 deletions

View File

@ -447,7 +447,7 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
enum CharState { ILLEGAL, CR, LF, LEGAL } enum CharState { ILLEGAL, CR, LF, LEGAL }
private final static CharState[] __charState; public final static CharState[] TOKEN_CHAR;
static static
{ {
// token = 1*tchar // token = 1*tchar
@ -462,38 +462,38 @@ public class HttpParser
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
__charState=new CharState[256]; TOKEN_CHAR =new CharState[256];
Arrays.fill(__charState,CharState.ILLEGAL); Arrays.fill(TOKEN_CHAR,CharState.ILLEGAL);
__charState[LINE_FEED]=CharState.LF; TOKEN_CHAR[LINE_FEED]=CharState.LF;
__charState[CARRIAGE_RETURN]=CharState.CR; TOKEN_CHAR[CARRIAGE_RETURN]=CharState.CR;
__charState[TAB]=CharState.LEGAL; TOKEN_CHAR[TAB]=CharState.LEGAL;
__charState[SPACE]=CharState.LEGAL; TOKEN_CHAR[SPACE]=CharState.LEGAL;
__charState['!']=CharState.LEGAL; TOKEN_CHAR['!']=CharState.LEGAL;
__charState['#']=CharState.LEGAL; TOKEN_CHAR['#']=CharState.LEGAL;
__charState['$']=CharState.LEGAL; TOKEN_CHAR['$']=CharState.LEGAL;
__charState['%']=CharState.LEGAL; TOKEN_CHAR['%']=CharState.LEGAL;
__charState['&']=CharState.LEGAL; TOKEN_CHAR['&']=CharState.LEGAL;
__charState['\'']=CharState.LEGAL; TOKEN_CHAR['\'']=CharState.LEGAL;
__charState['*']=CharState.LEGAL; TOKEN_CHAR['*']=CharState.LEGAL;
__charState['+']=CharState.LEGAL; TOKEN_CHAR['+']=CharState.LEGAL;
__charState['-']=CharState.LEGAL; TOKEN_CHAR['-']=CharState.LEGAL;
__charState['.']=CharState.LEGAL; TOKEN_CHAR['.']=CharState.LEGAL;
__charState['^']=CharState.LEGAL; TOKEN_CHAR['^']=CharState.LEGAL;
__charState['_']=CharState.LEGAL; TOKEN_CHAR['_']=CharState.LEGAL;
__charState['`']=CharState.LEGAL; TOKEN_CHAR['`']=CharState.LEGAL;
__charState['|']=CharState.LEGAL; TOKEN_CHAR['|']=CharState.LEGAL;
__charState['~']=CharState.LEGAL; TOKEN_CHAR['~']=CharState.LEGAL;
__charState['"']=CharState.LEGAL; TOKEN_CHAR['"']=CharState.LEGAL;
__charState['\\']=CharState.LEGAL; TOKEN_CHAR['\\']=CharState.LEGAL;
__charState['(']=CharState.LEGAL; TOKEN_CHAR['(']=CharState.LEGAL;
__charState[')']=CharState.LEGAL; TOKEN_CHAR[')']=CharState.LEGAL;
Arrays.fill(__charState,0x21,0x27+1,CharState.LEGAL); Arrays.fill(TOKEN_CHAR,0x21,0x27+1,CharState.LEGAL);
Arrays.fill(__charState,0x2A,0x5B+1,CharState.LEGAL); Arrays.fill(TOKEN_CHAR,0x2A,0x5B+1,CharState.LEGAL);
Arrays.fill(__charState,0x5D,0x7E+1,CharState.LEGAL); Arrays.fill(TOKEN_CHAR,0x5D,0x7E+1,CharState.LEGAL);
Arrays.fill(__charState,0x80,0xFF+1,CharState.LEGAL); Arrays.fill(TOKEN_CHAR,0x80,0xFF+1,CharState.LEGAL);
} }
@ -502,7 +502,7 @@ public class HttpParser
{ {
byte ch = buffer.get(); byte ch = buffer.get();
CharState s = __charState[0xff & ch]; CharState s = TOKEN_CHAR[0xff & ch];
switch(s) switch(s)
{ {
case ILLEGAL: case ILLEGAL:

View File

@ -52,7 +52,7 @@ import org.eclipse.jetty.util.log.Logger;
/** /**
* MultiPartInputStream * MultiPartInputStream
* * <p>
* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings. * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
* *
* @see <a href="https://tools.ietf.org/html/rfc7578">https://tools.ietf.org/html/rfc7578</a> * @see <a href="https://tools.ietf.org/html/rfc7578">https://tools.ietf.org/html/rfc7578</a>
@ -60,19 +60,18 @@ import org.eclipse.jetty.util.log.Logger;
public class MultiPartFormInputStream public class MultiPartFormInputStream
{ {
private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class); private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class);
private final int _bufferSize = 16 * 1024; private static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); private InputStream _in;
public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); private MultipartConfigElement _config;
protected InputStream _in; private String _contentType;
protected MultipartConfigElement _config; private MultiMap<Part> _parts;
protected String _contentType; private Throwable _err;
protected MultiMap<Part> _parts; private File _tmpDir;
protected Throwable _err; private File _contextTmpDir;
protected File _tmpDir; private boolean _deleteOnExit;
protected File _contextTmpDir; private boolean _writeFilesWithFilenames;
protected boolean _deleteOnExit; private boolean _parsed;
protected boolean _writeFilesWithFilenames; private int _bufferSize = 16 * 1024;
protected boolean _parsed;
public class MultiPart implements Part public class MultiPart implements Part
{ {
@ -86,7 +85,7 @@ public class MultiPartFormInputStream
protected long _size = 0; protected long _size = 0;
protected boolean _temporary = true; protected boolean _temporary = true;
public MultiPart(String name, String filename) throws IOException public MultiPart(String name, String filename)
{ {
_name = name; _name = name;
_filename = filename; _filename = filename;
@ -95,7 +94,7 @@ public class MultiPartFormInputStream
@Override @Override
public String toString() public String toString()
{ {
return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,tmp=%b,file=%s}",_name,_filename,_contentType,_size,_temporary,_file); return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,tmp=%b,file=%s}", _name, _filename, _contentType, _size, _temporary, _file);
} }
protected void setContentType(String contentType) protected void setContentType(String contentType)
@ -147,7 +146,7 @@ public class MultiPartFormInputStream
&& _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null) && _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null)
createFile(); createFile();
_out.write(bytes,offset,length); _out.write(bytes, offset, length);
_size += length; _size += length;
} }
@ -159,9 +158,9 @@ public class MultiPartFormInputStream
final boolean USER = true; final boolean USER = true;
final boolean WORLD = false; final boolean WORLD = false;
_file = File.createTempFile("MultiPart","",MultiPartFormInputStream.this._tmpDir); _file = File.createTempFile("MultiPart", "", MultiPartFormInputStream.this._tmpDir);
_file.setReadable(false,WORLD); // (reset) disable it for everyone first _file.setReadable(false, WORLD); // (reset) disable it for everyone first
_file.setReadable(true,USER); // enable for user only _file.setReadable(true, USER); // enable for user only
if (_deleteOnExit) if (_deleteOnExit)
_file.deleteOnExit(); _file.deleteOnExit();
@ -184,47 +183,32 @@ public class MultiPartFormInputStream
_headers = headers; _headers = headers;
} }
/**
* @see javax.servlet.http.Part#getContentType()
*/
@Override @Override
public String getContentType() public String getContentType()
{ {
return _contentType; return _contentType;
} }
/**
* @see javax.servlet.http.Part#getHeader(java.lang.String)
*/
@Override @Override
public String getHeader(String name) public String getHeader(String name)
{ {
if (name == null) if (name == null)
return null; return null;
return _headers.getValue(StringUtil.asciiToLowerCase(name),0); return _headers.getValue(StringUtil.asciiToLowerCase(name), 0);
} }
/**
* @see javax.servlet.http.Part#getHeaderNames()
*/
@Override @Override
public Collection<String> getHeaderNames() public Collection<String> getHeaderNames()
{ {
return _headers.keySet(); return _headers.keySet();
} }
/**
* @see javax.servlet.http.Part#getHeaders(java.lang.String)
*/
@Override @Override
public Collection<String> getHeaders(String name) public Collection<String> getHeaders(String name)
{ {
return _headers.getValues(name); return _headers.getValues(name);
} }
/**
* @see javax.servlet.http.Part#getInputStream()
*/
@Override @Override
public InputStream getInputStream() throws IOException public InputStream getInputStream() throws IOException
{ {
@ -236,13 +220,10 @@ public class MultiPartFormInputStream
else else
{ {
// part content is in memory // part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); return new ByteArrayInputStream(_bout.getBuf(), 0, _bout.size());
} }
} }
/**
* @see javax.servlet.http.Part#getSubmittedFileName()
*/
@Override @Override
public String getSubmittedFileName() public String getSubmittedFileName()
{ {
@ -256,27 +237,18 @@ public class MultiPartFormInputStream
return null; return null;
} }
/**
* @see javax.servlet.http.Part#getName()
*/
@Override @Override
public String getName() public String getName()
{ {
return _name; return _name;
} }
/**
* @see javax.servlet.http.Part#getSize()
*/
@Override @Override
public long getSize() public long getSize()
{ {
return _size; return _size;
} }
/**
* @see javax.servlet.http.Part#write(java.lang.String)
*/
@Override @Override
public void write(String fileName) throws IOException public void write(String fileName) throws IOException
{ {
@ -285,19 +257,15 @@ public class MultiPartFormInputStream
_temporary = false; _temporary = false;
// part data is only in the ByteArrayOutputStream and never been written to disk // part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File(_tmpDir,fileName); _file = new File(_tmpDir, fileName);
BufferedOutputStream bos = null; try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(_file)))
try
{ {
bos = new BufferedOutputStream(new FileOutputStream(_file));
_bout.writeTo(bos); _bout.writeTo(bos);
bos.flush(); bos.flush();
} }
finally finally
{ {
if (bos != null)
bos.close();
_bout = null; _bout = null;
} }
} }
@ -308,33 +276,32 @@ public class MultiPartFormInputStream
Path src = _file.toPath(); Path src = _file.toPath();
Path target = src.resolveSibling(fileName); Path target = src.resolveSibling(fileName);
Files.move(src,target,StandardCopyOption.REPLACE_EXISTING); Files.move(src, target, StandardCopyOption.REPLACE_EXISTING);
_file = target.toFile(); _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 @Override
public void delete() throws IOException public void delete() throws IOException
{ {
if (_file != null && _file.exists()) if (_file != null && _file.exists())
_file.delete(); if (!_file.delete())
throw new IOException("Could Not Delete File");
} }
/** /**
* Only remove tmp files. * Only remove tmp files.
* *
* @throws IOException * @throws IOException if unable to delete the file
* if unable to delete the file
*/ */
public void cleanUp() throws IOException public void cleanUp() throws IOException
{ {
if (_temporary && _file != null && _file.exists()) if (_temporary && _file != null && _file.exists())
_file.delete(); if (!_file.delete())
throw new IOException("Could Not Delete File");
} }
/** /**
@ -359,14 +326,10 @@ public class MultiPartFormInputStream
} }
/** /**
* @param in * @param in Request input stream
* Request input stream * @param contentType Content-Type header
* @param contentType * @param config MultipartConfigElement
* Content-Type header * @param contextTmpDir javax.servlet.context.tempdir
* @param config
* MultipartConfigElement
* @param contextTmpDir
* javax.servlet.context.tempdir
*/ */
public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
{ {
@ -402,7 +365,7 @@ public class MultiPartFormInputStream
Collection<List<Part>> values = _parts.values(); Collection<List<Part>> values = _parts.values();
for (List<Part> partList : values) for (List<Part> partList : values)
{ {
if(partList.size() != 0) if (partList.size() != 0)
return false; return false;
} }
@ -424,7 +387,7 @@ public class MultiPartFormInputStream
List<Part> parts = new ArrayList<>(); 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); parts.addAll(asList);
} }
return parts; return parts;
@ -447,8 +410,8 @@ public class MultiPartFormInputStream
{ {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
MultiException err = new MultiException();
MultiException err = null;
for (Part p : parts) for (Part p : parts)
{ {
try try
@ -457,11 +420,14 @@ public class MultiPartFormInputStream
} }
catch (Exception e) catch (Exception e)
{ {
if (err == null)
err = new MultiException();
err.add(e); err.add(e);
} }
} }
_parts.clear(); _parts.clear();
if (err != null)
err.ifExceptionThrowRuntime(); err.ifExceptionThrowRuntime();
} }
@ -469,8 +435,7 @@ public class MultiPartFormInputStream
* Parse, if necessary, the multipart data and return the list of Parts. * Parse, if necessary, the multipart data and return the list of Parts.
* *
* @return the parts * @return the parts
* @throws IOException * @throws IOException if unable to get the parts
* if unable to get the parts
*/ */
public Collection<Part> getParts() throws IOException public Collection<Part> getParts() throws IOException
{ {
@ -482,7 +447,7 @@ public class MultiPartFormInputStream
List<Part> parts = new ArrayList<>(); 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); parts.addAll(asList);
} }
return parts; return parts;
@ -491,25 +456,22 @@ public class MultiPartFormInputStream
/** /**
* Get the named Part. * Get the named Part.
* *
* @param name * @param name the part name
* the part name
* @return the parts * @return the parts
* @throws IOException * @throws IOException if unable to get the part
* if unable to get the part
*/ */
public Part getPart(String name) throws IOException public Part getPart(String name) throws IOException
{ {
if(!_parsed) if (!_parsed)
parse(); parse();
throwIfError(); throwIfError();
return _parts.getValue(name,0); return _parts.getValue(name, 0);
} }
/** /**
* Throws an exception if one has been latched. * Throws an exception if one has been latched.
* *
* @throws IOException * @throws IOException the exception (if present)
* the exception (if present)
*/ */
protected void throwIfError() throws IOException protected void throwIfError() throws IOException
{ {
@ -526,7 +488,6 @@ public class MultiPartFormInputStream
/** /**
* Parse, if necessary, the multipart stream. * Parse, if necessary, the multipart stream.
*
*/ */
protected void parse() protected void parse()
{ {
@ -537,7 +498,6 @@ public class MultiPartFormInputStream
try try
{ {
// initialize // initialize
_parts = new MultiMap<>(); _parts = new MultiMap<>();
@ -556,7 +516,7 @@ public class MultiPartFormInputStream
if (f.isAbsolute()) if (f.isAbsolute())
_tmpDir = f; _tmpDir = f;
else else
_tmpDir = new File(_contextTmpDir,_config.getLocation()); _tmpDir = new File(_contextTmpDir, _config.getLocation());
} }
if (!_tmpDir.exists()) if (!_tmpDir.exists())
@ -566,21 +526,16 @@ public class MultiPartFormInputStream
int bstart = _contentType.indexOf("boundary="); int bstart = _contentType.indexOf("boundary=");
if (bstart >= 0) if (bstart >= 0)
{ {
int bend = _contentType.indexOf(";",bstart); int bend = _contentType.indexOf(";", bstart);
bend = (bend < 0?_contentType.length():bend); bend = (bend < 0 ? _contentType.length() : bend);
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim()); contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart, bend)).trim());
} }
Handler handler = new Handler(); 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]; byte[] data = new byte[_bufferSize];
int len = 0; int len;
/*
* keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
*/
long total = 0; long total = 0;
while (true) while (true)
@ -590,6 +545,8 @@ public class MultiPartFormInputStream
if (len > 0) if (len > 0)
{ {
// keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
total += len; total += len;
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
{ {
@ -599,16 +556,16 @@ public class MultiPartFormInputStream
ByteBuffer buffer = BufferUtil.toBuffer(data); ByteBuffer buffer = BufferUtil.toBuffer(data);
buffer.limit(len); buffer.limit(len);
if (parser.parse(buffer,false)) if (parser.parse(buffer, false))
break; break;
if(buffer.hasRemaining()) if (buffer.hasRemaining())
throw new IllegalStateException("Buffer did not fully consume"); throw new IllegalStateException("Buffer did not fully consume");
} }
else if (len == -1) else if (len == -1)
{ {
parser.parse(BufferUtil.EMPTY_BUFFER,true); parser.parse(BufferUtil.EMPTY_BUFFER, true);
break; break;
} }
@ -631,16 +588,14 @@ public class MultiPartFormInputStream
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("Parsing Complete {} err={}",parser,_err); LOG.debug("Parsing Complete {} err={}", parser, _err);
} }
} }
catch (Throwable e) catch (Throwable e)
{ {
_err = e; _err = e;
return;
} }
} }
class Handler implements MultiPartParser.Handler class Handler implements MultiPartParser.Handler
@ -660,7 +615,7 @@ public class MultiPartFormInputStream
public void parsedField(String key, String value) public void parsedField(String key, String value)
{ {
// Add to headers and mark if one of these fields. // // Add to headers and mark if one of these fields. //
headers.put(StringUtil.asciiToLowerCase(key),value); headers.put(StringUtil.asciiToLowerCase(key), value);
if (key.equalsIgnoreCase("content-disposition")) if (key.equalsIgnoreCase("content-disposition"))
contentDisposition = value; contentDisposition = value;
else if (key.equalsIgnoreCase("content-type")) else if (key.equalsIgnoreCase("content-type"))
@ -674,9 +629,9 @@ public class MultiPartFormInputStream
@Override @Override
public boolean headerComplete() public boolean headerComplete()
{ {
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("headerComplete {}",this); LOG.debug("headerComplete {}", this);
} }
try try
@ -688,7 +643,7 @@ public class MultiPartFormInputStream
throw new IOException("Missing content-disposition"); throw new IOException("Missing content-disposition");
} }
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true); QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition, ";", false, true);
String name = null; String name = null;
String filename = null; String filename = null;
while (tok.hasMoreTokens()) while (tok.hasMoreTokens())
@ -717,10 +672,10 @@ public class MultiPartFormInputStream
// create the new part // create the new part
_part = new MultiPart(name,filename); _part = new MultiPart(name, filename);
_part.setHeaders(headers); _part.setHeaders(headers);
_part.setContentType(contentType); _part.setContentType(contentType);
_parts.add(name,_part); _parts.add(name, _part);
try try
{ {
@ -744,14 +699,14 @@ public class MultiPartFormInputStream
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
if(_part == null) if (_part == null)
return false; return false;
if (BufferUtil.hasContent(buffer)) if (BufferUtil.hasContent(buffer))
{ {
try try
{ {
_part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); _part.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
} }
catch (IOException e) catch (IOException e)
{ {
@ -786,7 +741,7 @@ public class MultiPartFormInputStream
public void earlyEOF() public void earlyEOF()
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Early EOF {}",MultiPartFormInputStream.this); LOG.debug("Early EOF {}", MultiPartFormInputStream.this);
} }
public void reset() public void reset()
@ -819,7 +774,7 @@ public class MultiPartFormInputStream
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private String value(String nameEqualsValue) private static String value(String nameEqualsValue)
{ {
int idx = nameEqualsValue.indexOf('='); int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx + 1).trim(); String value = nameEqualsValue.substring(idx + 1).trim();
@ -827,7 +782,7 @@ public class MultiPartFormInputStream
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private String filenameValue(String nameEqualsValue) private static String filenameValue(String nameEqualsValue)
{ {
int idx = nameEqualsValue.indexOf('='); int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx + 1).trim(); String value = nameEqualsValue.substring(idx + 1).trim();
@ -841,7 +796,7 @@ public class MultiPartFormInputStream
value = value.substring(1); value = value.substring(1);
char last = value.charAt(value.length() - 1); char last = value.charAt(value.length() - 1);
if (last == '"' || last == '\'') if (last == '"' || last == '\'')
value = value.substring(0,value.length() - 1); value = value.substring(0, value.length() - 1);
return value; return value;
} }
@ -850,7 +805,22 @@ public class MultiPartFormInputStream
// form a valid escape sequence to remain as many browsers // form a valid escape sequence to remain as many browsers
// even on *nix systems will not escape a filename containing // even on *nix systems will not escape a filename containing
// backslashes // backslashes
return QuotedStringTokenizer.unquoteOnly(value,true); return QuotedStringTokenizer.unquoteOnly(value, true);
} }
/**
* @return the size of buffer used to read data from the input stream
*/
public int getBufferSize()
{
return _bufferSize;
}
/**
* @param bufferSize the size of buffer used to read data from the input stream
*/
public void setBufferSize(int bufferSize)
{
_bufferSize = bufferSize;
}
} }

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import org.eclipse.jetty.http.HttpParser.RequestHandler; import org.eclipse.jetty.http.HttpParser.RequestHandler;
@ -31,7 +30,9 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** A parser for MultiPart content type.
/**
* A parser for MultiPart content type.
* *
* @see <a href="https://tools.ietf.org/html/rfc2046#section-5.1">https://tools.ietf.org/html/rfc2046#section-5.1</a> * @see <a href="https://tools.ietf.org/html/rfc2046#section-5.1">https://tools.ietf.org/html/rfc2046#section-5.1</a>
* @see <a href="https://tools.ietf.org/html/rfc2045">https://tools.ietf.org/html/rfc2045</a> * @see <a href="https://tools.ietf.org/html/rfc2045">https://tools.ietf.org/html/rfc2045</a>
@ -40,14 +41,11 @@ public class MultiPartParser
{ {
public static final Logger LOG = Log.getLogger(MultiPartParser.class); public static final Logger LOG = Log.getLogger(MultiPartParser.class);
static final byte COLON = (byte)':'; private static final byte COLON = (byte)':';
static final byte TAB = 0x09; private static final byte TAB = 0x09;
static final byte LINE_FEED = 0x0A; private static final byte LINE_FEED = 0x0A;
static final byte CARRIAGE_RETURN = 0x0D; private static final byte CARRIAGE_RETURN = 0x0D;
static final byte SPACE = 0x20; private static final byte SPACE = 0x20;
static final byte[] CRLF =
{ CARRIAGE_RETURN, LINE_FEED };
static final byte SEMI_COLON = (byte)';';
// States // States
public enum FieldState public enum FieldState
@ -73,7 +71,8 @@ public class MultiPartParser
END END
} }
private final static EnumSet<State> __delimiterStates = EnumSet.of(State.DELIMITER,State.DELIMITER_CLOSE,State.DELIMITER_PADDING); private final static EnumSet<State> __delimiterStates = EnumSet.of(State.DELIMITER, State.DELIMITER_CLOSE, State.DELIMITER_PADDING);
private final static int MAX_HEADER_LINE_LENGTH = 998;
private final boolean DEBUG = LOG.isDebugEnabled(); private final boolean DEBUG = LOG.isDebugEnabled();
private final Handler _handler; private final Handler _handler;
@ -92,7 +91,6 @@ public class MultiPartParser
private int _length; private int _length;
private int _totalHeaderLineLength = -1; private int _totalHeaderLineLength = -1;
private int _maxHeaderLineLength = 998;
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public MultiPartParser(Handler handler, String boundary) public MultiPartParser(Handler handler, String boundary)
@ -130,62 +128,7 @@ public class MultiPartParser
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
enum CharState private static boolean hasNextByte(ByteBuffer buffer)
{
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 )
__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['!'] = 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);
}
/* ------------------------------------------------------------------------------- */
private boolean hasNextByte(ByteBuffer buffer)
{ {
return BufferUtil.hasContent(buffer); return BufferUtil.hasContent(buffer);
} }
@ -196,7 +139,7 @@ public class MultiPartParser
byte ch = buffer.get(); byte ch = buffer.get();
CharState s = __charState[0xff & ch]; HttpParser.CharState s = HttpParser.TOKEN_CHAR[0xff & ch];
switch (s) switch (s)
{ {
case LF: case LF:
@ -223,7 +166,7 @@ public class MultiPartParser
case ILLEGAL: case ILLEGAL:
default: default:
throw new IllegalCharacterException(_state,ch,buffer); throw new IllegalCharacterException(_state, ch, buffer);
} }
} }
@ -243,14 +186,15 @@ public class MultiPartParser
{ {
String s = _string.toString(); String s = _string.toString();
// trim trailing whitespace. // trim trailing whitespace.
if (s.length()>_length) if (s.length() > _length)
s = s.substring(0,_length); s = s.substring(0, _length);
_string.reset(); _string.reset();
_length = -1; _length = -1;
return s; return s;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
/** /**
* Parse until next Event. * Parse until next Event.
* *
@ -261,7 +205,7 @@ public class MultiPartParser
public boolean parse(ByteBuffer buffer, boolean last) public boolean parse(ByteBuffer buffer, boolean last)
{ {
boolean handle = false; boolean handle = false;
while (handle == false && BufferUtil.hasContent(buffer)) while (!handle && BufferUtil.hasContent(buffer))
{ {
switch (_state) switch (_state)
{ {
@ -304,14 +248,14 @@ public class MultiPartParser
{ {
_state = State.END; _state = State.END;
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("messageComplete {}", this); LOG.debug("messageComplete {}", this);
return _handler.messageComplete(); return _handler.messageComplete();
} }
else else
{ {
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("earlyEOF {}", this); LOG.debug("earlyEOF {}", this);
_handler.earlyEOF(); _handler.earlyEOF();
@ -327,7 +271,7 @@ public class MultiPartParser
{ {
if (_partialBoundary > 0) if (_partialBoundary > 0)
{ {
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary); int partial = _delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), _partialBoundary);
if (partial > 0) if (partial > 0)
{ {
if (partial == _delimiterSearch.getLength()) if (partial == _delimiterSearch.getLength())
@ -346,7 +290,7 @@ public class MultiPartParser
_partialBoundary = 0; _partialBoundary = 0;
} }
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
if (delimiter >= 0) if (delimiter >= 0)
{ {
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength()); buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
@ -354,10 +298,8 @@ public class MultiPartParser
return; 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); BufferUtil.clear(buffer);
return;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
@ -373,8 +315,8 @@ public class MultiPartParser
{ {
setState(State.BODY_PART); setState(State.BODY_PART);
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("startPart {}",this); LOG.debug("startPart {}", this);
_handler.startPart(); _handler.startPart();
return; return;
@ -400,7 +342,6 @@ public class MultiPartParser
case DELIMITER_PADDING: case DELIMITER_PADDING:
default: default:
continue;
} }
} }
} }
@ -422,7 +363,7 @@ public class MultiPartParser
if (b != LINE_FEED) if (b != LINE_FEED)
_totalHeaderLineLength++; _totalHeaderLineLength++;
if (_totalHeaderLineLength > _maxHeaderLineLength) if (_totalHeaderLineLength > MAX_HEADER_LINE_LENGTH)
throw new IllegalStateException("Header Line Exceeded Max Length"); throw new IllegalStateException("Header Line Exceeded Max Length");
switch (_fieldState) switch (_fieldState)
@ -460,7 +401,7 @@ public class MultiPartParser
setState(State.FIRST_OCTETS); setState(State.FIRST_OCTETS);
_partialBoundary = 2; // CRLF is option for empty parts _partialBoundary = 2; // CRLF is option for empty parts
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("headerComplete {}", this); LOG.debug("headerComplete {}", this);
if (_handler.headerComplete()) if (_handler.headerComplete())
@ -498,7 +439,7 @@ public class MultiPartParser
case LINE_FEED: case LINE_FEED:
{ {
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Line Feed in Name {}", this); LOG.debug("Line Feed in Name {}", this);
handleField(); handleField();
@ -533,7 +474,7 @@ public class MultiPartParser
break; break;
default: default:
throw new IllegalCharacterException(_state,b,buffer); throw new IllegalCharacterException(_state, b, buffer);
} }
break; break;
@ -596,11 +537,11 @@ public class MultiPartParser
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private void handleField() private void handleField()
{ {
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this); LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this);
if (_fieldName != null && _fieldValue != null) if (_fieldName != null && _fieldValue != null)
_handler.parsedField(_fieldName,_fieldValue); _handler.parsedField(_fieldName, _fieldValue);
_fieldName = _fieldValue = null; _fieldName = _fieldValue = null;
} }
@ -612,7 +553,7 @@ public class MultiPartParser
// Starts With // Starts With
if (_partialBoundary > 0) if (_partialBoundary > 0)
{ {
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary); int partial = _delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), _partialBoundary);
if (partial > 0) if (partial > 0)
{ {
if (partial == _delimiterSearch.getLength()) if (partial == _delimiterSearch.getLength())
@ -621,10 +562,10 @@ public class MultiPartParser
setState(State.DELIMITER); setState(State.DELIMITER);
_partialBoundary = 0; _partialBoundary = 0;
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER),true,this); LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER), true, this);
return _handler.content(BufferUtil.EMPTY_BUFFER,true); return _handler.content(BufferUtil.EMPTY_BUFFER, true);
} }
_partialBoundary = partial; _partialBoundary = partial;
@ -643,16 +584,16 @@ public class MultiPartParser
content.limit(_partialBoundary); content.limit(_partialBoundary);
_partialBoundary = 0; _partialBoundary = 0;
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
if (_handler.content(content,false)) if (_handler.content(content, false))
return true; return true;
} }
} }
// Contains // Contains
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
if (delimiter >= 0) if (delimiter >= 0)
{ {
ByteBuffer content = buffer.slice(); ByteBuffer content = buffer.slice();
@ -661,41 +602,41 @@ public class MultiPartParser
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength()); buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER); setState(State.DELIMITER);
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),true,this); LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), true, this);
return _handler.content(content,true); return _handler.content(content, true);
} }
// Ends With // Ends With
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
if (_partialBoundary > 0) if (_partialBoundary > 0)
{ {
ByteBuffer content = buffer.slice(); ByteBuffer content = buffer.slice();
content.limit(content.limit() - _partialBoundary); content.limit(content.limit() - _partialBoundary);
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
return _handler.content(content,false); return _handler.content(content, false);
} }
// There is normal content with no delimiter // There is normal content with no delimiter
ByteBuffer content = buffer.slice(); ByteBuffer content = buffer.slice();
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
return _handler.content(content,false); return _handler.content(content, false);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private void setState(State state) private void setState(State state)
{ {
if (DEBUG) if (DEBUG)
LOG.debug("{} --> {}",_state,state); LOG.debug("{} --> {}", _state, state);
_state = state; _state = state;
} }
@ -703,7 +644,7 @@ public class MultiPartParser
private void setState(FieldState state) private void setState(FieldState state)
{ {
if (DEBUG) if (DEBUG)
LOG.debug("{}:{} --> {}",_state,_fieldState,state); LOG.debug("{}:{} --> {}", _state, _fieldState, state);
_fieldState = state; _fieldState = state;
} }
@ -711,7 +652,7 @@ public class MultiPartParser
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s{s=%s}",getClass().getSimpleName(),_state); return String.format("%s{s=%s}", getClass().getSimpleName(), _state);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -724,30 +665,32 @@ public class MultiPartParser
*/ */
public interface Handler public interface Handler
{ {
public default void startPart() default void startPart()
{ {
} }
public default void parsedField(String name, String value) @SuppressWarnings("unused")
default void parsedField(String name, String value)
{ {
} }
public default boolean headerComplete() default boolean headerComplete()
{ {
return false; return false;
} }
public default boolean content(ByteBuffer item, boolean last) @SuppressWarnings("unused")
default boolean content(ByteBuffer item, boolean last)
{ {
return false; return false;
} }
public default boolean messageComplete() default boolean messageComplete()
{ {
return false; return false;
} }
public default void earlyEOF() default void earlyEOF()
{ {
} }
} }
@ -758,9 +701,9 @@ public class MultiPartParser
{ {
private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) private IllegalCharacterException(State state, byte ch, ByteBuffer buffer)
{ {
super(String.format("Illegal character 0x%X",ch)); super(String.format("Illegal character 0x%X", ch));
// Bug #460642 - don't reveal buffers to end user // Bug #460642 - don't reveal buffers to end user
LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer))); LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s", ch, state, BufferUtil.toDetailString(buffer)));
} }
} }
} }

View File

@ -24,7 +24,6 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -41,30 +40,26 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement; import javax.servlet.MultipartConfigElement;
import javax.servlet.ReadListener; import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.http.Part; import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart; import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart;
import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
/** /**
* MultiPartInputStreamTest * MultiPartInputStreamTest
*
*
*/ */
public class MultiPartFormInputStreamTest public class MultiPartFormInputStreamTest
{ {
private static final String FILENAME = "stuff.txt"; private static final String FILENAME = "stuff.txt";
protected String _contentType = "multipart/form-data, boundary=AaB03x"; protected String _contentType = "multipart/form-data, boundary=AaB03x";
protected String _multi = createMultipartRequestString(FILENAME); protected String _multi = createMultipartRequestString(FILENAME);
protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); protected String _dirname = System.getProperty("java.io.tmpdir") + File.separator + "myfiles-" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
protected File _tmpDir = new File(_dirname); protected File _tmpDir = new File(_dirname);
public MultiPartFormInputStreamTest () public MultiPartFormInputStreamTest()
{ {
_tmpDir.deleteOnExit(); _tmpDir.deleteOnExit();
} }
@ -74,24 +69,24 @@ public class MultiPartFormInputStreamTest
throws Exception throws Exception
{ {
String boundary = "X0Y0"; String boundary = "X0Y0";
String str = "--" + boundary + "\r\n"+ String str = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+ "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n"+ "Content-Type: application/octet-stream\r\n\r\n" +
"How now brown cow."+ "How now brown cow." +
"\r\n--" + boundary + "-\r\n" "\r\n--" + boundary + "-\r\n"
+ "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n" + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"
+ "\r\n"; + "\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary, "multipart/form-data, boundary=" + boundary,
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
try try
{ {
mpis.getParts(); mpis.getParts();
fail ("Incomplete Multipart"); fail("Incomplete Multipart");
} }
catch (IOException e) catch (IOException e)
{ {
@ -118,16 +113,14 @@ public class MultiPartFormInputStreamTest
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary, "multipart/form-data, boundary=" + boundary,
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertTrue(mpis.getParts().isEmpty()); assertTrue(mpis.getParts().isEmpty());
} }
@Test @Test
public void testEmpty() public void testEmpty()
throws Exception throws Exception
@ -141,7 +134,7 @@ public class MultiPartFormInputStreamTest
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary, "multipart/form-data, boundary=" + boundary,
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
@ -152,30 +145,30 @@ public class MultiPartFormInputStreamTest
public void testNoBoundaryRequest() public void testNoBoundaryRequest()
throws Exception throws Exception
{ {
String str = "--\r\n"+ String str = "--\r\n" +
"Content-Disposition: form-data; name=\"fileName\"\r\n"+ "Content-Disposition: form-data; name=\"fileName\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n"+ "Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n"+ "Content-Transfer-Encoding: 8bit\r\n" +
"\r\n"+ "\r\n" +
"abc\r\n"+ "abc\r\n" +
"--\r\n"+ "--\r\n" +
"Content-Disposition: form-data; name=\"desc\"\r\n"+ "Content-Disposition: form-data; name=\"desc\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n"+ "Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n"+ "Content-Transfer-Encoding: 8bit\r\n" +
"\r\n"+ "\r\n" +
"123\r\n"+ "123\r\n" +
"--\r\n"+ "--\r\n" +
"Content-Disposition: form-data; name=\"title\"\r\n"+ "Content-Disposition: form-data; name=\"title\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n"+ "Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n"+ "Content-Transfer-Encoding: 8bit\r\n" +
"\r\n"+ "\r\n" +
"ttt\r\n"+ "ttt\r\n" +
"--\r\n"+ "--\r\n" +
"Content-Disposition: form-data; name=\"datafile5239138112980980385.txt\"; filename=\"datafile5239138112980980385.txt\"\r\n"+ "Content-Disposition: form-data; name=\"datafile5239138112980980385.txt\"; filename=\"datafile5239138112980980385.txt\"\r\n" +
"Content-Type: application/octet-stream; charset=ISO-8859-1\r\n"+ "Content-Type: application/octet-stream; charset=ISO-8859-1\r\n" +
"Content-Transfer-Encoding: binary\r\n"+ "Content-Transfer-Encoding: binary\r\n" +
"\r\n"+ "\r\n" +
"000\r\n"+ "000\r\n" +
"----\r\n"; "----\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -224,7 +217,6 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testNoBody() public void testNoBody()
throws Exception
{ {
String body = ""; String body = "";
@ -237,7 +229,7 @@ public class MultiPartFormInputStreamTest
try try
{ {
mpis.getParts(); mpis.getParts();
fail ("Missing initial multi part boundary"); fail("Missing initial multi part boundary");
} }
catch (IOException e) catch (IOException e)
{ {
@ -250,7 +242,8 @@ public class MultiPartFormInputStreamTest
public void testBodyAlreadyConsumed() public void testBodyAlreadyConsumed()
throws Exception throws Exception
{ {
ServletInputStream is = new ServletInputStream() { ServletInputStream is = new ServletInputStream()
{
@Override @Override
public boolean isFinished() public boolean isFinished()
@ -270,7 +263,7 @@ public class MultiPartFormInputStreamTest
} }
@Override @Override
public int read() throws IOException public int read()
{ {
return 0; return 0;
} }
@ -288,14 +281,11 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testWhitespaceBodyWithCRLF() public void testWhitespaceBodyWithCRLF()
throws Exception
{ {
String whitespace = " \n\n\n\r\n\r\n\r\n\r\n"; String whitespace = " \n\n\n\r\n\r\n\r\n\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()),
_contentType, _contentType,
@ -315,7 +305,6 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testWhitespaceBody() public void testWhitespaceBody()
throws Exception
{ {
String whitespace = " "; String whitespace = " ";
@ -328,7 +317,7 @@ public class MultiPartFormInputStreamTest
try try
{ {
mpis.getParts(); mpis.getParts();
fail ("Multipart missing body"); fail("Multipart missing body");
} }
catch (IOException e) catch (IOException e)
{ {
@ -340,16 +329,16 @@ public class MultiPartFormInputStreamTest
public void testLeadingWhitespaceBodyWithCRLF() public void testLeadingWhitespaceBodyWithCRLF()
throws Exception throws Exception
{ {
String body = " \n\n\n\r\n\r\n\r\n\r\n"+ String body = " \n\n\n\r\n\r\n\r\n\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n"+ "content-disposition: form-data; name=\"field1\"\r\n" +
"\r\n"+ "\r\n" +
"Joe Blow\r\n"+ "Joe Blow\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"aaaa"+ "\r\n" + "aaaa" +
"bbbbb"+"\r\n" + "bbbbb" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
@ -377,21 +366,20 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testLeadingWhitespaceBodyWithoutCRLF() public void testLeadingWhitespaceBodyWithoutCRLF()
throws Exception throws Exception
{ {
String body = " "+ String body = " " +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n"+ "content-disposition: form-data; name=\"field1\"\r\n" +
"\r\n"+ "\r\n" +
"Joe Blow\r\n"+ "Joe Blow\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"aaaa"+ "\r\n" + "aaaa" +
"bbbbb"+"\r\n" + "bbbbb" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -408,16 +396,11 @@ public class MultiPartFormInputStreamTest
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
Part stuff = mpis.getPart("stuff"); Part stuff = mpis.getPart("stuff");
assertThat(stuff, notNullValue()); assertThat(stuff, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(stuff.getInputStream(), baos); IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("bbbbb")); assertTrue(baos.toString("US-ASCII").contains("bbbbb"));
} }
@Test @Test
public void testNoLimits() public void testNoLimits()
throws Exception throws Exception
@ -433,7 +416,7 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testRequestTooBig () public void testRequestTooBig()
throws Exception throws Exception
{ {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
@ -456,7 +439,7 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testRequestTooBigThrowsErrorOnGetParts () public void testRequestTooBigThrowsErrorOnGetParts()
throws Exception throws Exception
{ {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
@ -465,12 +448,11 @@ public class MultiPartFormInputStreamTest
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
//cause parsing //cause parsing
try try
{ {
parts = mpis.getParts(); mpis.getParts();
fail("Request should have exceeded maxRequestSize"); fail("Request should have exceeded maxRequestSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -481,7 +463,7 @@ public class MultiPartFormInputStreamTest
//try again //try again
try try
{ {
parts = mpis.getParts(); mpis.getParts();
fail("Request should have exceeded maxRequestSize"); fail("Request should have exceeded maxRequestSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -500,10 +482,9 @@ public class MultiPartFormInputStreamTest
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try try
{ {
parts = mpis.getParts(); mpis.getParts();
fail("stuff.txt should have been larger than maxFileSize"); fail("stuff.txt should have been larger than maxFileSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -522,10 +503,9 @@ public class MultiPartFormInputStreamTest
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try try
{ {
parts = mpis.getParts(); //caused parsing mpis.getParts(); //caused parsing
fail("stuff.txt should have been larger than maxFileSize"); fail("stuff.txt should have been larger than maxFileSize");
} }
catch (Throwable e) catch (Throwable e)
@ -536,7 +516,7 @@ public class MultiPartFormInputStreamTest
//test again after the parsing //test again after the parsing
try try
{ {
parts = mpis.getParts(); //caused parsing mpis.getParts(); //caused parsing
fail("stuff.txt should have been larger than maxFileSize"); fail("stuff.txt should have been larger than maxFileSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -546,9 +526,8 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testPartFileNotDeleted () throws Exception public void testPartFileNotDeleted() throws Exception
{ {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
@ -556,13 +535,13 @@ public class MultiPartFormInputStreamTest
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff"); MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile(); File stuff = part.getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file
part.write("tptfd.txt"); part.write("tptfd.txt");
File tptfd = new File (_dirname+File.separator+"tptfd.txt"); File tptfd = new File(_dirname + File.separator + "tptfd.txt");
assertThat(tptfd.exists(), is(true)); assertThat(tptfd.exists(), is(true));
assertThat(stuff.exists(), is(false)); //got renamed assertThat(stuff.exists(), is(false)); //got renamed
part.cleanUp(); part.cleanUp();
@ -571,7 +550,7 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testPartTmpFileDeletion () throws Exception public void testPartTmpFileDeletion() throws Exception
{ {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
@ -579,12 +558,12 @@ public class MultiPartFormInputStreamTest
config, config,
_tmpDir); _tmpDir);
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff"); MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile(); File stuff = part.getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat (stuff.exists(), is(true)); assertThat(stuff.exists(), is(true));
part.cleanUp(); part.cleanUp();
assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup
} }
@ -593,14 +572,14 @@ public class MultiPartFormInputStreamTest
public void testLFOnlyRequest() public void testLFOnlyRequest()
throws Exception throws Exception
{ {
String str = "--AaB03x\n"+ String str = "--AaB03x\n" +
"content-disposition: form-data; name=\"field1\"\n"+ "content-disposition: form-data; name=\"field1\"\n" +
"\n"+ "\n" +
"Joe Blow"+ "Joe Blow" +
"\r\n--AaB03x\n"+ "\r\n--AaB03x\n" +
"content-disposition: form-data; name=\"field2\"\n"+ "content-disposition: form-data; name=\"field2\"\n" +
"\n"+ "\n" +
"Other"+ "Other" +
"\r\n--AaB03x--\n"; "\r\n--AaB03x--\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -628,16 +607,15 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testCROnlyRequest() public void testCROnlyRequest()
throws Exception
{ {
String str = "--AaB03x\r"+ String str = "--AaB03x\r" +
"content-disposition: form-data; name=\"field1\"\r"+ "content-disposition: form-data; name=\"field1\"\r" +
"\r"+ "\r" +
"Joe Blow\r"+ "Joe Blow\r" +
"--AaB03x\r"+ "--AaB03x\r" +
"content-disposition: form-data; name=\"field2\"\r"+ "content-disposition: form-data; name=\"field2\"\r" +
"\r"+ "\r" +
"Other\r"+ "Other\r" +
"--AaB03x--\r"; "--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -666,7 +644,7 @@ public class MultiPartFormInputStreamTest
IO.copy(p2.getInputStream(), baos); IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other")); assertThat(baos.toString("UTF-8"), is("Other"));
} }
catch(Throwable e) catch (Throwable e)
{ {
assertTrue(e.getMessage().contains("Bad EOL")); assertTrue(e.getMessage().contains("Bad EOL"));
} }
@ -674,17 +652,16 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testCRandLFMixRequest() public void testCRandLFMixRequest()
throws Exception
{ {
String str = "--AaB03x\r"+ String str = "--AaB03x\r" +
"content-disposition: form-data; name=\"field1\"\r"+ "content-disposition: form-data; name=\"field1\"\r" +
"\r"+ "\r" +
"\nJoe Blow\n"+ "\nJoe Blow\n" +
"\r"+ "\r" +
"--AaB03x\r"+ "--AaB03x\r" +
"content-disposition: form-data; name=\"field2\"\r"+ "content-disposition: form-data; name=\"field2\"\r" +
"\r"+ "\r" +
"Other\r"+ "Other\r" +
"--AaB03x--\r"; "--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -712,18 +689,18 @@ public class MultiPartFormInputStreamTest
IO.copy(p2.getInputStream(), baos); IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other")); assertThat(baos.toString("UTF-8"), is("Other"));
} }
catch(Throwable e) catch (Throwable e)
{ {
assertTrue(e.getMessage().contains("Bad EOL")); assertTrue(e.getMessage().contains("Bad EOL"));
} }
} }
@Test @Test
public void testBufferOverflowNoCRLF () throws Exception public void testBufferOverflowNoCRLF() throws Exception
{ {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("--AaB03x\r\n".getBytes()); baos.write("--AaB03x\r\n".getBytes());
for (int i=0; i< 3000; i++) //create content that will overrun default buffer size of BufferedInputStream for (int i = 0; i < 3000; i++) //create content that will overrun default buffer size of BufferedInputStream
{ {
baos.write('a'); baos.write('a');
} }
@ -737,7 +714,7 @@ public class MultiPartFormInputStreamTest
try try
{ {
mpis.getParts(); mpis.getParts();
fail ("Header Line Exceeded Max Length"); fail("Header Line Exceeded Max Length");
} }
catch (Throwable e) catch (Throwable e)
{ {
@ -747,14 +724,14 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testCharsetEncoding () throws Exception public void testCharsetEncoding() throws Exception
{ {
String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1"; String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1";
String str = "--TheBoundary\r\n"+ String str = "--TheBoundary\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n"+ "content-disposition: form-data; name=\"field1\"\r\n" +
"\r\n"+ "\r\n" +
"\nJoe Blow\n"+ "\nJoe Blow\n" +
"\r\n"+ "\r\n" +
"--TheBoundary--\r\n"; "--TheBoundary--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -772,11 +749,11 @@ public class MultiPartFormInputStreamTest
public void testBadlyEncodedFilename() throws Exception public void testBadlyEncodedFilename() throws Exception
{ {
String contents = "--AaB03x\r\n"+ String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" +"Taken on Aug 22 \\ 2012.jpg" + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + "Taken on Aug 22 \\ 2012.jpg" + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"stuff"+ "\r\n" + "stuff" +
"aaa"+"\r\n" + "aaa" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -787,18 +764,18 @@ public class MultiPartFormInputStreamTest
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1)); assertThat(parts.size(), is(1));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg")); assertThat(parts.iterator().next().getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
} }
@Test @Test
public void testBadlyEncodedMSFilename() throws Exception public void testBadlyEncodedMSFilename() throws Exception
{ {
String contents = "--AaB03x\r\n"+ String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"stuff"+ "\r\n" + "stuff" +
"aaa"+"\r\n" + "aaa" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -809,17 +786,17 @@ public class MultiPartFormInputStreamTest
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1)); assertThat(parts.size(), is(1));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); assertThat(parts.iterator().next().getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
} }
@Test @Test
public void testCorrectlyEncodedMSFilename() throws Exception public void testCorrectlyEncodedMSFilename() throws Exception
{ {
String contents = "--AaB03x\r\n"+ String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"stuff"+ "\r\n" + "stuff" +
"aaa"+"\r\n" + "aaa" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -830,13 +807,7 @@ public class MultiPartFormInputStreamTest
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1)); assertThat(parts.size(), is(1));
assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); assertThat(parts.iterator().next().getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
public void testMulti ()
throws Exception
{
testMulti(FILENAME);
} }
@Test @Test
@ -847,18 +818,18 @@ public class MultiPartFormInputStreamTest
@Test @Test
public void testWriteFilesIfContentDispositionFilename () public void testWriteFilesIfContentDispositionFilename()
throws Exception throws Exception
{ {
String s = "--AaB03x\r\n"+ String s = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n"+ "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" +
"\r\n"+ "\r\n" +
"Joe Blow\r\n"+ "Joe Blow\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"\r\n"+ "content-disposition: form-data; name=\"stuff\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+"sss"+ "\r\n" + "sss" +
"aaa"+"\r\n" + "aaa" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
//all default values for multipartconfig, ie file size threshold 0 //all default values for multipartconfig, ie file size threshold 0
MultipartConfigElement config = new MultipartConfigElement(_dirname); MultipartConfigElement config = new MultipartConfigElement(_dirname);
@ -872,7 +843,7 @@ public class MultiPartFormInputStreamTest
assertThat(parts.size(), is(2)); assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); //has a filename, should be written to a file Part field1 = mpis.getPart("field1"); //has a filename, should be written to a file
File f = ((MultiPartFormInputStream.MultiPart)field1).getFile(); File f = ((MultiPartFormInputStream.MultiPart)field1).getFile();
assertThat(f,notNullValue()); // longer than 100 bytes, should already be a tmp file assertThat(f, notNullValue()); // longer than 100 bytes, should already be a tmp file
Part stuff = mpis.getPart("stuff"); Part stuff = mpis.getPart("stuff");
f = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); //should only be in memory, no filename f = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); //should only be in memory, no filename
@ -880,7 +851,7 @@ public class MultiPartFormInputStreamTest
} }
private void testMulti(String filename) throws IOException, ServletException, InterruptedException private void testMulti(String filename) throws IOException
{ {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
@ -891,21 +862,24 @@ public class MultiPartFormInputStreamTest
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2)); assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer
assertThat(field1,notNullValue()); assertThat(field1, notNullValue());
assertThat(field1.getName(),is("field1")); assertThat(field1.getName(), is("field1"));
InputStream is = field1.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
try (InputStream is = field1.getInputStream())
{
IO.copy(is, os); IO.copy(is, os);
}
assertEquals("Joe Blow", new String(os.toByteArray())); assertEquals("Joe Blow", new String(os.toByteArray()));
assertEquals(8, field1.getSize()); assertEquals(8, field1.getSize());
assertNotNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//in internal buffer assertNotNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//in internal buffer
field1.write("field1.txt"); field1.write("field1.txt");
assertNull(((MultiPartFormInputStream.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"); File f = new File(_dirname + File.separator + "field1.txt");
assertTrue(f.exists()); assertTrue(f.exists());
field1.write("another_field1.txt"); //write after having already written field1.write("another_field1.txt"); //write after having already written
File f2 = new File(_dirname+File.separator+"another_field1.txt"); File f2 = new File(_dirname + File.separator + "another_field1.txt");
assertTrue(f2.exists()); assertTrue(f2.exists());
assertFalse(f.exists()); //should have been renamed assertFalse(f.exists()); //should have been renamed
field1.delete(); //file should be deleted field1.delete(); //file should be deleted
@ -914,21 +888,21 @@ public class MultiPartFormInputStreamTest
MultiPart stuff = (MultiPart)mpis.getPart("stuff"); MultiPart stuff = (MultiPart)mpis.getPart("stuff");
assertThat(stuff.getSubmittedFileName(), is(filename)); assertThat(stuff.getSubmittedFileName(), is(filename));
assertThat(stuff.getContentType(),is("text/plain")); assertThat(stuff.getContentType(), is("text/plain"));
assertThat(stuff.getHeader("Content-Type"),is("text/plain")); assertThat(stuff.getHeader("Content-Type"), is("text/plain"));
assertThat(stuff.getHeaders("content-type").size(),is(1)); assertThat(stuff.getHeaders("content-type").size(), is(1));
assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\"")); assertThat(stuff.getHeader("content-disposition"), is("form-data; name=\"stuff\"; filename=\"" + filename + "\""));
assertThat(stuff.getHeaderNames().size(),is(2)); assertThat(stuff.getHeaderNames().size(), is(2));
assertThat(stuff.getSize(),is(51L)); assertThat(stuff.getSize(), is(51L));
File tmpfile = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); File tmpfile = stuff.getFile();
assertThat(tmpfile,notNullValue()); // longer than 50 bytes, should already be a tmp file assertThat(tmpfile, notNullValue()); // longer than 50 bytes, should already be a tmp file
assertThat(stuff.getBytes(),nullValue()); //not in an internal buffer assertThat(stuff.getBytes(), nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(),is(true)); assertThat(tmpfile.exists(), is(true));
assertThat(tmpfile.getName(),is(not("stuff with space.txt"))); assertThat(tmpfile.getName(), is(not("stuff with space.txt")));
stuff.write(filename); stuff.write(filename);
f = new File(_dirname+File.separator+filename); f = new File(_dirname + File.separator + filename);
assertThat(f.exists(),is(true)); assertThat(f.exists(), is(true));
assertThat(tmpfile.exists(), is(false)); assertThat(tmpfile.exists(), is(false));
try try
{ {
@ -936,25 +910,25 @@ public class MultiPartFormInputStreamTest
} }
catch (Exception e) catch (Exception e)
{ {
fail("Part.getInputStream() after file rename operation"); fail("Part.getInputStream() after file rename operation: " + e.getMessage());
} }
f.deleteOnExit(); //clean up after test f.deleteOnExit(); //clean up after test
} }
@Test @Test
public void testMultiSameNames () public void testMultiSameNames()
throws Exception throws Exception
{ {
String sameNames = "--AaB03x\r\n"+ String sameNames = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff1.txt\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"stuff1.txt\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"00000\r\n"+ "00000\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"110000000000000000000000000000000000000000000000000\r\n"+ "110000000000000000000000000000000000000000000000000\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -965,7 +939,7 @@ public class MultiPartFormInputStreamTest
mpis.setDeleteOnExit(true); mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertEquals(2, parts.size()); assertEquals(2, parts.size());
for (Part p:parts) for (Part p : parts)
assertEquals("stuff", p.getName()); assertEquals("stuff", p.getName());
//if they all have the name name, then only retrieve the first one //if they all have the name name, then only retrieve the first one
@ -975,25 +949,25 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testBase64EncodedContent () throws Exception public void testBase64EncodedContent() throws Exception
{ {
String contentWithEncodedPart = String contentWithEncodedPart =
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"Content-disposition: form-data; name=\"other\"\r\n"+ "Content-disposition: form-data; name=\"other\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"other" + "\r\n"+ "other" + "\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+ "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" +
"Content-Transfer-Encoding: base64\r\n"+ "Content-Transfer-Encoding: base64\r\n" +
"Content-Type: application/octet-stream\r\n"+ "Content-Type: application/octet-stream\r\n" +
"\r\n"+ "\r\n" +
B64Code.encode("hello jetty") + "\r\n"+ B64Code.encode("hello jetty") + "\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"Content-disposition: form-data; name=\"final\"\r\n"+ "Content-disposition: form-data; name=\"final\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"the end" + "\r\n"+ "the end" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
@ -1025,20 +999,20 @@ public class MultiPartFormInputStreamTest
} }
@Test @Test
public void testQuotedPrintableEncoding () throws Exception public void testQuotedPrintableEncoding() throws Exception
{ {
String contentWithEncodedPart = String contentWithEncodedPart =
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"Content-disposition: form-data; name=\"other\"\r\n"+ "Content-disposition: form-data; name=\"other\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"other" + "\r\n"+ "other" + "\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+ "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n"+ "Content-Transfer-Encoding: quoted-printable\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+ "\r\n" +
"truth=3Dbeauty" + "\r\n"+ "truth=3Dbeauty" + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
@ -1099,13 +1073,13 @@ public class MultiPartFormInputStreamTest
} }
private String createMultipartRequestString(String filename) private static String createMultipartRequestString(String filename)
{ {
int length = filename.length(); int length = filename.length();
String name = filename; String name = filename;
if (length > 10) if (length > 10)
name = filename.substring(0,10); name = filename.substring(0, 10);
StringBuffer filler = new StringBuffer(); StringBuilder filler = new StringBuilder();
int i = name.length(); int i = name.length();
while (i < 51) while (i < 51)
{ {
@ -1113,15 +1087,15 @@ public class MultiPartFormInputStreamTest
i++; i++;
} }
return "--AaB03x\r\n"+ return "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n"+ "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" +
"\r\n"+ "\r\n" +
"Joe Blow\r\n"+ "Joe Blow\r\n" +
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n" +
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n" +
"\r\n"+name+ "\r\n" + name +
filler.toString()+"\r\n" + filler.toString() + "\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
} }
} }

View File

@ -39,115 +39,123 @@ public class MultiPartParserTest
@Test @Test
public void testEmptyPreamble() public void testEmptyPreamble()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
{
}, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
} }
@Test @Test
public void testNoPreamble() public void testNoPreamble()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
ByteBuffer data = BufferUtil.toBuffer(""); {
}, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY \r\n");
data = BufferUtil.toBuffer("--BOUNDARY \r\n"); parser.parse(data, false);
parser.parse(data,false);
assertTrue(parser.isState(State.BODY_PART)); assertTrue(parser.isState(State.BODY_PART));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
} }
@Test @Test
public void testPreamble() public void testPreamble()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
{
}, "BOUNDARY");
ByteBuffer data; ByteBuffer data;
data = BufferUtil.toBuffer("This is not part of a part\r\n"); data = BufferUtil.toBuffer("This is not part of a part\r\n");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("More data that almost includes \n--BOUNDARY but no CR before."); data = BufferUtil.toBuffer("More data that almost includes \n--BOUNDARY but no CR before.");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("Could be a boundary \r\n--BOUNDAR"); data = BufferUtil.toBuffer("Could be a boundary \r\n--BOUNDAR");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("but not it isn't \r\n--BOUN"); data = BufferUtil.toBuffer("but not it isn't \r\n--BOUN");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("DARX nor is this"); data = BufferUtil.toBuffer("DARX nor is this");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
} }
@Test @Test
public void testPreambleCompleteBoundary() public void testPreambleCompleteBoundary()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
ByteBuffer data; {
}, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n");
data = BufferUtil.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n"); parser.parse(data, false);
parser.parse(data,false); assertThat(parser.getState(), is(State.BODY_PART));
assertThat(parser.getState(),is(State.BODY_PART)); assertThat(data.remaining(), is(0));
assertThat(data.remaining(),is(0));
} }
@Test @Test
public void testPreambleSplitBoundary() public void testPreambleSplitBoundary()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
ByteBuffer data; {
}, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n");
data = BufferUtil.toBuffer("This is not part of a part\r\n"); parser.parse(data, false);
parser.parse(data,false); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(data.remaining(), is(0));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("-"); data = BufferUtil.toBuffer("-");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("-"); data = BufferUtil.toBuffer("-");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("B"); data = BufferUtil.toBuffer("B");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(), is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("OUNDARY-"); data = BufferUtil.toBuffer("OUNDARY-");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.DELIMITER_CLOSE)); assertThat(parser.getState(), is(State.DELIMITER_CLOSE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("ignore\r"); data = BufferUtil.toBuffer("ignore\r");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.DELIMITER_PADDING)); assertThat(parser.getState(), is(State.DELIMITER_PADDING));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
data = BufferUtil.toBuffer("\n"); data = BufferUtil.toBuffer("\n");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.BODY_PART)); assertThat(parser.getState(), is(State.BODY_PART));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
} }
@Test @Test
public void testFirstPartNoFields() public void testFirstPartNoFields()
{ {
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
ByteBuffer data = BufferUtil.toBuffer(""); {
}, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n");
data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n"); parser.parse(data, false);
parser.parse(data,false); assertThat(parser.getState(), is(State.FIRST_OCTETS));
assertThat(parser.getState(),is(State.FIRST_OCTETS)); assertThat(data.remaining(), is(0));
assertThat(data.remaining(),is(0));
} }
@Test @Test
@ -162,60 +170,55 @@ public class MultiPartParserTest
return true; return true;
} }
}; };
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name0: value0\r\n" + "name0: value0\r\n"
+ "name1 :value1 \r\n" + "name1 :value1 \r\n"
+ "name2:value\r\n" + "name2:value\r\n"
+ " 2\r\n" + " 2\r\n"
+ "\r\n" + "\r\n"
+ "Content"); + "Content");
parser.parse(data,false);
assertThat(parser.getState(),is(State.FIRST_OCTETS)); parser.parse(data, false);
assertThat(data.remaining(),is(7)); assertThat(parser.getState(), is(State.FIRST_OCTETS));
assertThat(handler.fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<<COMPLETE>>")); assertThat(data.remaining(), is(7));
assertThat(handler.fields, Matchers.contains("name0: value0", "name1: value1", "name2: value 2", "<<COMPLETE>>"));
} }
@Test @Test
public void testFirstPartNoContent() public void testFirstPartNoContent()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n" + "name: value\r\n"
+ "\r\n" + "\r\n"
+ "\r\n" + "\r\n"
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("<<LAST>>")); assertThat(handler.content, Matchers.contains("<<LAST>>"));
} }
@Test @Test
public void testFirstPartNoContentNoCRLF() public void testFirstPartNoContentNoCRLF()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n" + "name: value\r\n"
+ "\r\n" + "\r\n"
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("<<LAST>>")); assertThat(handler.content, Matchers.contains("<<LAST>>"));
} }
@ -223,53 +226,49 @@ public class MultiPartParserTest
public void testFirstPartContentLookingLikeNoCRLF() public void testFirstPartContentLookingLikeNoCRLF()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n" + "name: value\r\n"
+ "\r\n" + "\r\n"
+ "-"); + "-");
parser.parse(data,false); parser.parse(data, false);
data = BufferUtil.toBuffer("Content!"); data = BufferUtil.toBuffer("Content!");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.OCTETS)); assertThat(parser.getState(), is(State.OCTETS));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("-","Content!")); assertThat(handler.content, Matchers.contains("-", "Content!"));
} }
@Test @Test
public void testFirstPartPartialContent() public void testFirstPartPartialContent()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n" + "name: value\n"
+ "\r\n" + "\r\n"
+ "Hello\r\n"); + "Hello\r\n");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.OCTETS)); assertThat(parser.getState(), is(State.OCTETS));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello")); assertThat(handler.content, Matchers.contains("Hello"));
data = BufferUtil.toBuffer( data = BufferUtil.toBuffer(
"Now is the time for all good ment to come to the aid of the party.\r\n" "Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n" + "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n" + "The quick brown fox jumped over the lazy dog.\r\n"
+ "this is not a --BOUNDARY\r\n"); + "this is not a --BOUNDARY\r\n");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(),is(State.OCTETS)); assertThat(parser.getState(), is(State.OCTETS));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","\r\n","Now is the time for all good ment to come to the aid of the party.\r\n" assertThat(handler.content, Matchers.contains("Hello", "\r\n", "Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n" + "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n" + "The quick brown fox jumped over the lazy dog.\r\n"
+ "this is not a --BOUNDARY")); + "this is not a --BOUNDARY"));
@ -279,20 +278,18 @@ public class MultiPartParserTest
public void testFirstPartShortContent() public void testFirstPartShortContent()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n" + "name: value\n"
+ "\r\n" + "\r\n"
+ "Hello\r\n" + "Hello\r\n"
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>")); assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>"));
} }
@ -300,11 +297,9 @@ public class MultiPartParserTest
public void testFirstPartLongContent() public void testFirstPartLongContent()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""); ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n" + "name: value\n"
+ "\r\n" + "\r\n"
+ "Now is the time for all good ment to come to the aid of the party.\r\n" + "Now is the time for all good ment to come to the aid of the party.\r\n"
@ -312,25 +307,23 @@ public class MultiPartParserTest
+ "The quick brown fox jumped over the lazy dog.\r\n" + "The quick brown fox jumped over the lazy dog.\r\n"
+ "\r\n" + "\r\n"
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Now is the time for all good ment to come to the aid of the party.\r\n" assertThat(handler.content, Matchers.contains("Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n" + "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n","<<LAST>>")); + "The quick brown fox jumped over the lazy dog.\r\n", "<<LAST>>"));
} }
@Test @Test
public void testFirstPartLongContentNoCarriageReturn() public void testFirstPartLongContentNoCarriageReturn()
{ {
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
//boundary still requires carriage return //boundary still requires carriage return
data = BufferUtil.toBuffer("--BOUNDARY\n" ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\n"
+ "name: value\n" + "name: value\n"
+ "\n" + "\n"
+ "Now is the time for all good men to come to the aid of the party.\n" + "Now is the time for all good men to come to the aid of the party.\n"
@ -338,13 +331,13 @@ public class MultiPartParserTest
+ "The quick brown fox jumped over the lazy dog.\n" + "The quick brown fox jumped over the lazy dog.\n"
+ "\r\n" + "\r\n"
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Now is the time for all good men to come to the aid of the party.\n" assertThat(handler.content, Matchers.contains("Now is the time for all good men to come to the aid of the party.\n"
+ "How now brown cow.\n" + "How now brown cow.\n"
+ "The quick brown fox jumped over the lazy dog.\n","<<LAST>>")); + "The quick brown fox jumped over the lazy dog.\n", "<<LAST>>"));
} }
@ -361,30 +354,31 @@ public class MultiPartParserTest
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
BufferUtil.append(bytes,buffer); BufferUtil.append(bytes, buffer);
return last; return last;
} }
}; };
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n"; String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n";
String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n"; String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n";
ByteBuffer data = BufferUtil.allocate(preamble.length()+random.length+epilogue.length()); ByteBuffer data = BufferUtil.allocate(preamble.length() + random.length + epilogue.length());
BufferUtil.append(data,BufferUtil.toBuffer(preamble)); BufferUtil.append(data, BufferUtil.toBuffer(preamble));
BufferUtil.append(data,ByteBuffer.wrap(random)); BufferUtil.append(data, ByteBuffer.wrap(random));
BufferUtil.append(data,BufferUtil.toBuffer(epilogue)); BufferUtil.append(data, BufferUtil.toBuffer(epilogue));
parser.parse(data,true); parser.parse(data, true);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(19)); assertThat(data.remaining(), is(19));
assertThat(bytes.array(),is(random)); assertThat(bytes.array(), is(random));
} }
@Test @Test
public void testEpilogue() { public void testEpilogue()
{
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("" ByteBuffer data = BufferUtil.toBuffer(""
+ "--BOUNDARY\r\n" + "--BOUNDARY\r\n"
@ -399,20 +393,21 @@ public class MultiPartParserTest
+ "--BOUNDARY"); + "--BOUNDARY");
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>")); assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>"));
parser.parse(data,true); parser.parse(data, true);
assertThat(parser.getState(), is(State.END)); assertThat(parser.getState(), is(State.END));
} }
@Test @Test
public void testMultipleContent() { public void testMultipleContent()
{
TestHandler handler = new TestHandler(); TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("" ByteBuffer data = BufferUtil.toBuffer(""
+ "--BOUNDARY\r\n" + "--BOUNDARY\r\n"
@ -430,62 +425,63 @@ public class MultiPartParserTest
+ "epilogue here"); + "epilogue here");
/* Test First Content Section */ /* Test First Content Section */
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>")); assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>"));
/* Test Second Content Section */ /* Test Second Content Section */
parser.parse(data,false); parser.parse(data, false);
assertThat(parser.getState(), is(State.DELIMITER)); assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>","powerLevel: 9001","<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>", "powerLevel: 9001", "<<COMPLETE>>"));
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>","secondary\r\ncontent","<<LAST>>")); assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>", "secondary\r\ncontent", "<<LAST>>"));
/* Test Progression to END State */ /* Test Progression to END State */
parser.parse(data,true); parser.parse(data, true);
assertThat(parser.getState(), is(State.END)); assertThat(parser.getState(), is(State.END));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(), is(0));
} }
@Test @Test
public void testCrAsLineTermination() { public void testCrAsLineTermination()
{
TestHandler handler = new TestHandler() TestHandler handler = new TestHandler()
{ {
@Override public boolean messageComplete(){ return true; } @Override
public boolean messageComplete()
{
return true;
}
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
super.content(buffer,last); super.content(buffer, last);
return false; return false;
} }
}; };
MultiPartParser parser = new MultiPartParser(handler,"AaB03x"); MultiPartParser parser = new MultiPartParser(handler, "AaB03x");
ByteBuffer data = BufferUtil.toBuffer( ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r\n"+ "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n"+ "content-disposition: form-data; name=\"field1\"\r\n" +
"\r"+ "\r" +
"Joe Blow\r\n"+ "Joe Blow\r\n" +
"--AaB03x--\r\n"); "--AaB03x--\r\n");
try try
{ {
parser.parse(data,true); parser.parse(data, true);
fail("Invalid End of Line"); fail("Invalid End of Line");
} }
catch(BadMessageException e) { catch (BadMessageException e)
{
assertTrue(e.getMessage().contains("Bad EOL")); assertTrue(e.getMessage().contains("Bad EOL"));
} }
} }
@Test @Test
public void splitTest() public void splitTest()
{ {
@ -500,13 +496,13 @@ public class MultiPartParserTest
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
super.content(buffer,last); super.content(buffer, last);
return false; return false;
} }
}; };
MultiPartParser parser = new MultiPartParser(handler,"---------------------------9051914041544843365972754266"); MultiPartParser parser = new MultiPartParser(handler, "---------------------------9051914041544843365972754266");
ByteBuffer data = BufferUtil.toBuffer(""+ ByteBuffer data = BufferUtil.toBuffer("" +
"POST / HTTP/1.1\n" + "POST / HTTP/1.1\n" +
"Host: localhost:8000\n" + "Host: localhost:8000\n" +
"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\n" + "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\n" +
@ -548,7 +544,7 @@ public class MultiPartParserTest
"\r\n" + "\r\n" +
"-----------------------------9051914041544843365972754266\n" + "-----------------------------9051914041544843365972754266\n" +
"Field1: value1\n" + "Field1: value1\n" +
"\r\n"+ "\r\n" +
"But the amount of denudation which the strata have\n" + "But the amount of denudation which the strata have\n" +
"in many places suffered, independently of the rate\n" + "in many places suffered, independently of the rate\n" +
"of accumulation of the degraded matter, probably\n" + "of accumulation of the degraded matter, probably\n" +
@ -567,39 +563,40 @@ public class MultiPartParserTest
int length = data.remaining(); int length = data.remaining();
for(int i = 0; i<length-1; i++){ for (int i = 0; i < length - 1; i++)
{
//partition 0 to i //partition 0 to i
ByteBuffer dataSeg = data.slice(); ByteBuffer dataSeg = data.slice();
dataSeg.position(0); dataSeg.position(0);
dataSeg.limit(i); dataSeg.limit(i);
assertThat("First "+i,parser.parse(dataSeg,false),is(false)); assertThat("First " + i, parser.parse(dataSeg, false), is(false));
//partition i //partition i
dataSeg = data.slice(); dataSeg = data.slice();
dataSeg.position(i); dataSeg.position(i);
dataSeg.limit(i+1); dataSeg.limit(i + 1);
assertThat("Second "+i,parser.parse(dataSeg,false),is(false)); assertThat("Second " + i, parser.parse(dataSeg, false), is(false));
//partition i to length //partition i to length
dataSeg = data.slice(); dataSeg = data.slice();
dataSeg.position(i+1); dataSeg.position(i + 1);
dataSeg.limit(length); dataSeg.limit(length);
assertThat("Third "+i,parser.parse(dataSeg,true),is(true)); assertThat("Third " + i, parser.parse(dataSeg, true), is(true));
assertThat(handler.fields, Matchers.contains( "Content-Disposition: form-data; name=\"text\"","<<COMPLETE>>" assertThat(handler.fields, Matchers.contains("Content-Disposition: form-data; name=\"text\"", "<<COMPLETE>>"
, "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"" , "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
, "Content-Type: text/plain","<<COMPLETE>>" , "Content-Type: text/plain", "<<COMPLETE>>"
, "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"" , "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\""
, "Content-Type: text/html","<<COMPLETE>>" , "Content-Type: text/html", "<<COMPLETE>>"
, "Field1: value1", "Field2: value2", "Field3: value3" , "Field1: value1", "Field2: value2", "Field3: value3"
, "Field4: value4", "Field5: value5", "Field6: value6" , "Field4: value4", "Field5: value5", "Field6: value6"
, "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>" , "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>"
, "Field1: value1","<<COMPLETE>>")); , "Field1: value1", "<<COMPLETE>>"));
assertThat(handler.contentString(), is(new String("text default"+"<<LAST>>" assertThat(handler.contentString(), is("text default" + "<<LAST>>"
+ "Content of a.txt.\n"+"<<LAST>>" + "Content of a.txt.\n" + "<<LAST>>"
+ "<!DOCTYPE html><title>Content of a.html.</title>\n"+"<<LAST>>" + "<!DOCTYPE html><title>Content of a.html.</title>\n" + "<<LAST>>"
+ "<<LAST>>" + "<<LAST>>"
+ "But the amount of denudation which the strata have\n" + + "But the amount of denudation which the strata have\n" +
"in many places suffered, independently of the rate\n" + "in many places suffered, independently of the rate\n" +
@ -612,7 +609,7 @@ public class MultiPartParserTest
"in height; for the gentle slope of the lava-streams,\n" + "in height; for the gentle slope of the lava-streams,\n" +
"due to their formerly liquid state, showed at a glance\n" + "due to their formerly liquid state, showed at a glance\n" +
"how far the hard, rocky beds had once extended into\n" + "how far the hard, rocky beds had once extended into\n" +
"the open ocean.\n"+ "<<LAST>>"))); "the open ocean.\n" + "<<LAST>>"));
handler.clear(); handler.clear();
parser.reset(); parser.reset();
@ -634,7 +631,7 @@ public class MultiPartParserTest
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
super.content(buffer,last); super.content(buffer, last);
return false; return false;
} }
@ -645,7 +642,7 @@ public class MultiPartParserTest
} }
}; };
MultiPartParser parser = new MultiPartParser(handler,"WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"); MultiPartParser parser = new MultiPartParser(handler, "WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW");
ByteBuffer data = BufferUtil.toBuffer("" ByteBuffer data = BufferUtil.toBuffer(""
+ "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + + "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
"\r\n" + "\r\n" +
@ -659,7 +656,7 @@ public class MultiPartParserTest
"&ᄈᄎ￙ᅱᅢO\r\n" + "&ᄈᄎ￙ᅱᅢO\r\n" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--"); "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--");
parser.parse(data,true); parser.parse(data, true);
assertThat(parser.getState(), is(State.END)); assertThat(parser.getState(), is(State.END));
assertThat(handler.fields.size(), is(2)); assertThat(handler.fields.size(), is(2));
@ -674,13 +671,14 @@ public class MultiPartParserTest
@Override @Override
public void parsedField(String name, String value) public void parsedField(String name, String value)
{ {
fields.add(name+": "+value); fields.add(name + ": " + value);
} }
public String contentString() public String contentString()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(String s : content) sb.append(s); for (String s : content)
sb.append(s);
return sb.toString(); return sb.toString();
} }
@ -701,11 +699,10 @@ public class MultiPartParserTest
return last; return last;
} }
public void clear() { public void clear()
{
fields.clear(); fields.clear();
content.clear(); content.clear();
} }
} }
} }

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.http.jmh;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
@ -34,11 +33,6 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartFormInputStream; import org.eclipse.jetty.http.MultiPartFormInputStream;
import org.eclipse.jetty.http.MultiPartCaptureTest.MultipartExpectations; import org.eclipse.jetty.http.MultiPartCaptureTest.MultipartExpectations;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.Rule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Level;
@ -48,7 +42,6 @@ import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.profile.CompilerProfiler;
import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.Options;
@ -70,6 +63,7 @@ public class MultiPartBenchmark
public static List<String> data = new ArrayList<>(); public static List<String> data = new ArrayList<>();
static static
{ {
// Capture of raw request body contents from various browsers // Capture of raw request body contents from various browsers
@ -111,17 +105,17 @@ public class MultiPartBenchmark
} }
@Param({"UTIL","HTTP"}) @Param({"UTIL", "HTTP"})
public static String parserType; public static String parserType;
@Setup(Level.Trial) @Setup(Level.Trial)
public static void setupTrial() throws Exception public static void setupTrial() throws Exception
{ {
_file = File.createTempFile("test01",null); _file = File.createTempFile("test01", null);
_file.deleteOnExit(); _file.deleteOnExit();
_numSections = 1; _numSections = 1;
_numBytesPerSection = 1024*1024*10; _numBytesPerSection = 1024 * 1024 * 10;
_contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"; _contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW";
String initialBoundary = "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n"; String initialBoundary = "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n";
@ -130,16 +124,17 @@ public class MultiPartBenchmark
String headerStart = "Content-Disposition: form-data; name=\""; String headerStart = "Content-Disposition: form-data; name=\"";
for(int i=0; i<_numSections; i++) { for (int i = 0; i < _numSections; i++)
{
//boundary and headers //boundary and headers
if(i==0) if (i == 0)
Files.write(_file.toPath(), initialBoundary.getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), initialBoundary.getBytes(), StandardOpenOption.APPEND);
else else
Files.write(_file.toPath(), boundary.getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), boundary.getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), headerStart.getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), headerStart.getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), new String("part"+(i+1)).getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), ("part" + (i + 1)).getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), new String("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), ("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND);
//append random data //append random data
byte[] data = new byte[_numBytesPerSection]; byte[] data = new byte[_numBytesPerSection];
@ -149,22 +144,6 @@ public class MultiPartBenchmark
//closing boundary //closing boundary
Files.write(_file.toPath(), closingBoundary.getBytes(), StandardOpenOption.APPEND); Files.write(_file.toPath(), closingBoundary.getBytes(), StandardOpenOption.APPEND);
/*
// print out file to verify that it contains valid contents (just for testing)
InputStream in = Files.newInputStream(_file.toPath());
System.out.println();
while(in.available()>0) {
byte b[] = new byte[100];
int read = in.read(b,0,100);
for(int i=0; i<read; i++)
System.out.print((char)b[i]);
}
System.out.println();
//exit
throw new RuntimeException("Stop Here");
*/
} }
@ -180,31 +159,32 @@ public class MultiPartBenchmark
try (InputStream in = Files.newInputStream(multipartRawFile)) try (InputStream in = Files.newInputStream(multipartRawFile))
{ {
switch(parserType) switch (parserType)
{ {
case "HTTP": case "HTTP":
{ {
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, _contentType, config, outputDir.toFile()); MultiPartFormInputStream parser = new MultiPartFormInputStream(in, _contentType, config, outputDir.toFile());
if(parser.getParts().size() != _numSections) if (parser.getParts().size() != _numSections)
throw new IllegalStateException("Incorrect Parsing"); throw new IllegalStateException("Incorrect Parsing");
for(Part p : parser.getParts()) { for (Part p : parser.getParts())
{
count += p.getSize(); count += p.getSize();
} }
} }
break; break;
case "UTIL": case "UTIL":
{ {
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, _contentType,config,outputDir.toFile()); org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, _contentType, config, outputDir.toFile());
if (parser.getParts().size() != _numSections)
// TODO this is using the http version of part (which should be the same anyway)
if(parser.getParts().size() != _numSections)
throw new IllegalStateException("Incorrect Parsing"); throw new IllegalStateException("Incorrect Parsing");
for(Part p : parser.getParts()) { for (Part p : parser.getParts())
{
count += p.getSize(); count += p.getSize();
} }
} }
break; break;
default: default:
throw new IllegalStateException("Unknown parserType Parameter"); throw new IllegalStateException("Unknown parserType Parameter");
} }
@ -214,7 +194,6 @@ public class MultiPartBenchmark
} }
@TearDown(Level.Trial) @TearDown(Level.Trial)
public static void stopTrial() throws Exception public static void stopTrial() throws Exception
{ {
@ -231,7 +210,7 @@ public class MultiPartBenchmark
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public long testParser() throws Exception public long testParser() throws Exception
{ {
for(String multiPart : data) for (String multiPart : data)
{ {
Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw"); Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw");
Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt"); Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt");
@ -243,22 +222,22 @@ public class MultiPartBenchmark
try (InputStream in = Files.newInputStream(multipartRawFile)) try (InputStream in = Files.newInputStream(multipartRawFile))
{ {
switch(parserType) switch (parserType)
{ {
case "HTTP": case "HTTP":
{ {
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, multipartExpectations.contentType, config, outputDir.toFile()); MultiPartFormInputStream parser = new MultiPartFormInputStream(in, multipartExpectations.contentType, config, outputDir.toFile());
for(Part p : parser.getParts()) { for (Part p : parser.getParts())
{
count += p.getSize(); count += p.getSize();
} }
} }
break; break;
case "UTIL": case "UTIL":
{ {
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in,multipartExpectations.contentType,config,outputDir.toFile()); org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, multipartExpectations.contentType, config, outputDir.toFile());
for (Part p : parser.getParts())
// TODO this is using the http version of part (which should be the same anyway) {
for(Part p : parser.getParts()) {
count += p.getSize(); count += p.getSize();
} }
} }
@ -280,14 +259,6 @@ public class MultiPartBenchmark
.measurementIterations(10) .measurementIterations(10)
.forks(1) .forks(1)
.threads(1) .threads(1)
// .syncIterations(true) // Don't start all threads at same time
// .warmupTime(new TimeValue(10000,TimeUnit.MILLISECONDS))
// .measurementTime(new TimeValue(10000,TimeUnit.MILLISECONDS))
// .addProfiler(CompilerProfiler.class)
// .addProfiler(LinuxPerfProfiler.class)
// .addProfiler(LinuxPerfNormProfiler.class)
// .addProfiler(LinuxPerfAsmProfiler.class)
// .resultFormat(ResultFormatType.CSV)
.build(); .build();
new Runner(opt).run(); new Runner(opt).run();