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,28 +52,27 @@ 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>
*/ */
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
{ {
protected String _name; protected String _name;
@ -85,24 +84,24 @@ public class MultiPartFormInputStream
protected MultiMap<String> _headers; protected MultiMap<String> _headers;
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;
} }
@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)
{ {
_contentType = contentType; _contentType = contentType;
} }
protected void open() throws IOException protected void open() throws IOException
{ {
// We will either be writing to a file, if it has a filename on the content-disposition // We will either be writing to a file, if it has a filename on the content-disposition
@ -119,38 +118,38 @@ public class MultiPartFormInputStream
_out = _bout = new ByteArrayOutputStream2(); _out = _bout = new ByteArrayOutputStream2();
} }
} }
protected void close() throws IOException protected void close() throws IOException
{ {
_out.close(); _out.close();
} }
protected void write(int b) throws IOException protected void write(int b) throws IOException
{ {
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize()) if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold() if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold()
&& _file == null) && _file == null)
createFile(); createFile();
_out.write(b); _out.write(b);
_size++; _size++;
} }
protected void write(byte[] bytes, int offset, int length) throws IOException protected void write(byte[] bytes, int offset, int length) throws IOException
{ {
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize()) if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0
&& _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;
} }
protected void createFile() throws IOException protected void createFile() throws IOException
{ {
/* /*
@ -158,16 +157,16 @@ 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();
FileOutputStream fos = new FileOutputStream(_file); FileOutputStream fos = new FileOutputStream(_file);
BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedOutputStream bos = new BufferedOutputStream(fos);
if (_size > 0 && _out != null) if (_size > 0 && _out != null)
{ {
// already written some bytes, so need to copy them into the file // already written some bytes, so need to copy them into the file
@ -178,53 +177,38 @@ public class MultiPartFormInputStream
_bout = null; _bout = null;
_out = bos; _out = bos;
} }
protected void setHeaders(MultiMap<String> headers) protected void setHeaders(MultiMap<String> headers)
{ {
_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,68 +220,52 @@ 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()
{ {
return getContentDispositionFilename(); return getContentDispositionFilename();
} }
public byte[] getBytes() public byte[] getBytes()
{ {
if (_bout != null) if (_bout != null)
return _bout.toByteArray(); return _bout.toByteArray();
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
{ {
if (_file == null) if (_file == null)
{ {
_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;
} }
} }
@ -305,51 +273,50 @@ public class MultiPartFormInputStream
{ {
// the part data is already written to a temporary file, just rename it // the part data is already written to a temporary file, just rename it
_temporary = false; _temporary = false;
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");
} }
/** /**
* Get the file * Get the file
* *
* @return the file, if any, the data has been written to. * @return the file, if any, the data has been written to.
*/ */
public File getFile() public File getFile()
{ {
return _file; return _file;
} }
/** /**
* Get the filename from the content-disposition. * Get the filename from the content-disposition.
* *
* @return null or the filename * @return null or the filename
*/ */
public String getContentDispositionFilename() public String getContentDispositionFilename()
@ -357,16 +324,12 @@ public class MultiPartFormInputStream
return _filename; return _filename;
} }
} }
/** /**
* @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)
{ {
@ -375,10 +338,10 @@ public class MultiPartFormInputStream
_contextTmpDir = contextTmpDir; _contextTmpDir = contextTmpDir;
if (_contextTmpDir == null) if (_contextTmpDir == null)
_contextTmpDir = new File(System.getProperty("java.io.tmpdir")); _contextTmpDir = new File(System.getProperty("java.io.tmpdir"));
if (_config == null) if (_config == null)
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
if (in instanceof ServletInputStream) if (in instanceof ServletInputStream)
{ {
if (((ServletInputStream)in).isFinished()) if (((ServletInputStream)in).isFinished())
@ -390,7 +353,7 @@ public class MultiPartFormInputStream
} }
_in = new BufferedInputStream(in); _in = new BufferedInputStream(in);
} }
/** /**
* @return whether the list of parsed parts is empty * @return whether the list of parsed parts is empty
*/ */
@ -402,16 +365,16 @@ 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;
} }
return true; return true;
} }
/** /**
* Get the already parsed parts. * Get the already parsed parts.
* *
* @return the parts that were parsed * @return the parts that were parsed
*/ */
@Deprecated @Deprecated
@ -419,25 +382,25 @@ public class MultiPartFormInputStream
{ {
if (_parts == null) if (_parts == null)
return Collections.emptyList(); return Collections.emptyList();
Collection<List<Part>> values = _parts.values(); Collection<List<Part>> values = _parts.values();
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;
} }
/** /**
* Delete any tmp storage for parts, and clear out the parts list. * Delete any tmp storage for parts, and clear out the parts list.
*/ */
public void deleteParts() public void deleteParts()
{ {
if (!_parsed) if (!_parsed)
return; return;
Collection<Part> parts; Collection<Part> parts;
try try
{ {
@ -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,59 +420,58 @@ 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();
err.ifExceptionThrowRuntime(); if (err != null)
err.ifExceptionThrowRuntime();
} }
/** /**
* 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
{ {
if (!_parsed) if (!_parsed)
parse(); parse();
throwIfError(); throwIfError();
Collection<List<Part>> values = _parts.values(); Collection<List<Part>> values = _parts.values();
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;
} }
/** /**
* 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
{ {
@ -523,10 +485,9 @@ public class MultiPartFormInputStream
throw new IllegalStateException(_err); throw new IllegalStateException(_err);
} }
} }
/** /**
* Parse, if necessary, the multipart stream. * Parse, if necessary, the multipart stream.
*
*/ */
protected void parse() protected void parse()
{ {
@ -537,14 +498,13 @@ public class MultiPartFormInputStream
try try
{ {
// initialize // initialize
_parts = new MultiMap<>(); _parts = new MultiMap<>();
// if its not a multipart request, don't parse it // if its not a multipart request, don't parse it
if (_contentType == null || !_contentType.startsWith("multipart/form-data")) if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
return; return;
// sort out the location to which to write the files // sort out the location to which to write the files
if (_config.getLocation() == null) if (_config.getLocation() == null)
_tmpDir = _contextTmpDir; _tmpDir = _contextTmpDir;
@ -556,70 +516,67 @@ 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())
_tmpDir.mkdirs(); _tmpDir.mkdirs();
String contentTypeBoundary = ""; String contentTypeBoundary = "";
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)
{ {
len = _in.read(data); len = _in.read(data);
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())
{ {
_err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")"); _err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");
return; return;
} }
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;
} }
} }
// check for exceptions // check for exceptions
if (_err != null) if (_err != null)
{ {
return; return;
} }
// check we read to the end of the message // check we read to the end of the message
if (parser.getState() != MultiPartParser.State.END) if (parser.getState() != MultiPartParser.State.END)
{ {
@ -628,39 +585,37 @@ public class MultiPartFormInputStream
else else
_err = new IOException("Incomplete Multipart"); _err = new IOException("Incomplete Multipart");
} }
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
{ {
private MultiPart _part = null; private MultiPart _part = null;
private String contentDisposition = null; private String contentDisposition = null;
private String contentType = null; private String contentType = null;
private MultiMap<String> headers = new MultiMap<>(); private MultiMap<String> headers = new MultiMap<>();
@Override @Override
public boolean messageComplete() public boolean messageComplete()
{ {
return true; return true;
} }
@Override @Override
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"))
@ -668,27 +623,27 @@ public class MultiPartFormInputStream
// Transfer encoding is not longer considers as it is deprecated as per // Transfer encoding is not longer considers as it is deprecated as per
// https://tools.ietf.org/html/rfc7578#section-4.7 // https://tools.ietf.org/html/rfc7578#section-4.7
} }
@Override @Override
public boolean headerComplete() public boolean headerComplete()
{ {
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("headerComplete {}",this); LOG.debug("headerComplete {}", this);
} }
try try
{ {
// Extract content-disposition // Extract content-disposition
boolean form_data = false; boolean form_data = false;
if (contentDisposition == null) if (contentDisposition == null)
{ {
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())
@ -702,7 +657,7 @@ public class MultiPartFormInputStream
else if (tl.startsWith("filename=")) else if (tl.startsWith("filename="))
filename = filenameValue(t); filename = filenameValue(t);
} }
// Check disposition // Check disposition
if (!form_data) if (!form_data)
throw new IOException("Part not form-data"); throw new IOException("Part not form-data");
@ -714,13 +669,13 @@ public class MultiPartFormInputStream
// have not yet seen a name field. // have not yet seen a name field.
if (name == null) if (name == null)
throw new IOException("No name in part"); throw new IOException("No name in part");
// 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
{ {
@ -737,21 +692,21 @@ public class MultiPartFormInputStream
_err = e; _err = e;
return true; return true;
} }
return false; return false;
} }
@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)
{ {
@ -759,7 +714,7 @@ public class MultiPartFormInputStream
return true; return true;
} }
} }
if (last) if (last)
{ {
try try
@ -772,12 +727,12 @@ public class MultiPartFormInputStream
return true; return true;
} }
} }
return false; return false;
} }
@Override @Override
public void startPart() public void startPart()
{ {
reset(); reset();
} }
@ -786,9 +741,9 @@ 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()
{ {
_part = null; _part = null;
@ -797,41 +752,41 @@ public class MultiPartFormInputStream
headers = new MultiMap<>(); headers = new MultiMap<>();
} }
} }
public void setDeleteOnExit(boolean deleteOnExit) public void setDeleteOnExit(boolean deleteOnExit)
{ {
_deleteOnExit = deleteOnExit; _deleteOnExit = deleteOnExit;
} }
public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames) public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames)
{ {
_writeFilesWithFilenames = writeFilesWithFilenames; _writeFilesWithFilenames = writeFilesWithFilenames;
} }
public boolean isWriteFilesWithFilenames() public boolean isWriteFilesWithFilenames()
{ {
return _writeFilesWithFilenames; return _writeFilesWithFilenames;
} }
public boolean isDeleteOnExit() public boolean isDeleteOnExit()
{ {
return _deleteOnExit; return _deleteOnExit;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
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();
return QuotedStringTokenizer.unquoteOnly(value); return QuotedStringTokenizer.unquoteOnly(value);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
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();
if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
{ {
// incorrectly escaped IE filenames that have the whole path // incorrectly escaped IE filenames that have the whole path
@ -841,8 +796,8 @@ 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;
} }
else else
@ -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,202 +30,146 @@ 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>
*/ */
public class MultiPartParser 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
{ {
FIELD, FIELD,
IN_NAME, IN_NAME,
AFTER_NAME, AFTER_NAME,
VALUE, VALUE,
IN_VALUE IN_VALUE
} }
// States // States
public enum State public enum State
{ {
PREAMBLE, PREAMBLE,
DELIMITER, DELIMITER,
DELIMITER_PADDING, DELIMITER_PADDING,
DELIMITER_CLOSE, DELIMITER_CLOSE,
BODY_PART, BODY_PART,
FIRST_OCTETS, FIRST_OCTETS,
OCTETS, OCTETS,
EPILOGUE, EPILOGUE,
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;
private final SearchPattern _delimiterSearch; private final SearchPattern _delimiterSearch;
private String _fieldName; private String _fieldName;
private String _fieldValue; private String _fieldValue;
private State _state = State.PREAMBLE; private State _state = State.PREAMBLE;
private FieldState _fieldState = FieldState.FIELD; private FieldState _fieldState = FieldState.FIELD;
private int _partialBoundary = 2; // No CRLF if no preamble private int _partialBoundary = 2; // No CRLF if no preamble
private boolean _cr; private boolean _cr;
private ByteBuffer _patternBuffer; private ByteBuffer _patternBuffer;
private final Utf8StringBuilder _string = new Utf8StringBuilder(); private final Utf8StringBuilder _string = new Utf8StringBuilder();
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)
{ {
_handler = handler; _handler = handler;
String delimiter = "\r\n--" + boundary; String delimiter = "\r\n--" + boundary;
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII)); _patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
_delimiterSearch = SearchPattern.compile(_patternBuffer.array()); _delimiterSearch = SearchPattern.compile(_patternBuffer.array());
} }
public void reset() public void reset()
{ {
_state = State.PREAMBLE; _state = State.PREAMBLE;
_fieldState = FieldState.FIELD; _fieldState = FieldState.FIELD;
_partialBoundary = 2; // No CRLF if no preamble _partialBoundary = 2; // No CRLF if no preamble
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public Handler getHandler() public Handler getHandler()
{ {
return _handler; return _handler;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public State getState() public State getState()
{ {
return _state; return _state;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public boolean isState(State state) public boolean isState(State state)
{ {
return _state == state; return _state == state;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
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);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private byte getNextByte(ByteBuffer buffer) private byte getNextByte(ByteBuffer buffer)
{ {
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:
_cr = false; _cr = false;
return ch; return ch;
case CR: case CR:
if (_cr) if (_cr)
throw new BadMessageException("Bad EOL"); throw new BadMessageException("Bad EOL");
_cr = true; _cr = true;
if (buffer.hasRemaining()) if (buffer.hasRemaining())
return getNextByte(buffer); return getNextByte(buffer);
// Can return 0 here to indicate the need for more characters, // Can return 0 here to indicate the need for more characters,
// because a real 0 in the buffer would cause a BadMessage below // because a real 0 in the buffer would cause a BadMessage below
return 0; return 0;
case LEGAL: case LEGAL:
if (_cr) if (_cr)
throw new BadMessageException("Bad EOL"); throw new BadMessageException("Bad EOL");
return ch; return ch;
case ILLEGAL: case ILLEGAL:
default: default:
throw new IllegalCharacterException(_state,ch,buffer); throw new IllegalCharacterException(_state, ch, buffer);
} }
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private void setString(String s) private void setString(String s)
{ {
@ -234,7 +177,7 @@ public class MultiPartParser
_string.append(s); _string.append(s);
_length = s.length(); _length = s.length();
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
/* /*
* Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1 * Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1
@ -243,91 +186,92 @@ 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.
* *
* @param buffer the buffer to parse * @param buffer the buffer to parse
* @param last whether this buffer contains last bit of content * @param last whether this buffer contains last bit of content
* @return True if an {@link RequestHandler} method was called and it returned true; * @return True if an {@link RequestHandler} method was called and it returned true;
*/ */
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)
{ {
case PREAMBLE: case PREAMBLE:
parsePreamble(buffer); parsePreamble(buffer);
continue; continue;
case DELIMITER: case DELIMITER:
case DELIMITER_PADDING: case DELIMITER_PADDING:
case DELIMITER_CLOSE: case DELIMITER_CLOSE:
parseDelimiter(buffer); parseDelimiter(buffer);
continue; continue;
case BODY_PART: case BODY_PART:
handle = parseMimePartHeaders(buffer); handle = parseMimePartHeaders(buffer);
break; break;
case FIRST_OCTETS: case FIRST_OCTETS:
case OCTETS: case OCTETS:
handle = parseOctetContent(buffer); handle = parseOctetContent(buffer);
break; break;
case EPILOGUE: case EPILOGUE:
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
break; break;
case END: case END:
handle = true; handle = true;
break; break;
default: default:
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }
if (last && BufferUtil.isEmpty(buffer)) if (last && BufferUtil.isEmpty(buffer))
{ {
if (_state == State.EPILOGUE) if (_state == State.EPILOGUE)
{ {
_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();
return true; return true;
} }
} }
return handle; return handle;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private void parsePreamble(ByteBuffer buffer) private void parsePreamble(ByteBuffer buffer)
{ {
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())
@ -337,29 +281,27 @@ public class MultiPartParser
setState(State.DELIMITER); setState(State.DELIMITER);
return; return;
} }
_partialBoundary = partial; _partialBoundary = partial;
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
return; return;
} }
_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());
setState(State.DELIMITER); setState(State.DELIMITER);
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;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private void parseDelimiter(ByteBuffer buffer) private void parseDelimiter(ByteBuffer buffer)
{ {
@ -368,18 +310,18 @@ public class MultiPartParser
byte b = getNextByte(buffer); byte b = getNextByte(buffer);
if (b == 0) if (b == 0)
return; return;
if (b == '\n') if (b == '\n')
{ {
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;
} }
switch (_state) switch (_state)
{ {
case DELIMITER: case DELIMITER:
@ -388,7 +330,7 @@ public class MultiPartParser
else else
setState(State.DELIMITER_PADDING); setState(State.DELIMITER_PADDING);
continue; continue;
case DELIMITER_CLOSE: case DELIMITER_CLOSE:
if (b == '-') if (b == '-')
{ {
@ -397,14 +339,13 @@ public class MultiPartParser
} }
setState(State.DELIMITER_PADDING); setState(State.DELIMITER_PADDING);
continue; continue;
case DELIMITER_PADDING: case DELIMITER_PADDING:
default: default:
continue;
} }
} }
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
/* /*
* Parse the message headers and return true if the handler has signaled for a return * Parse the message headers and return true if the handler has signaled for a return
@ -418,13 +359,13 @@ public class MultiPartParser
byte b = getNextByte(buffer); byte b = getNextByte(buffer);
if (b == 0) if (b == 0)
break; break;
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)
{ {
case FIELD: case FIELD:
@ -434,10 +375,10 @@ public class MultiPartParser
case TAB: case TAB:
{ {
// Folded field value! // Folded field value!
if (_fieldName == null) if (_fieldName == null)
throw new IllegalStateException("First field folded"); throw new IllegalStateException("First field folded");
if (_fieldValue == null) if (_fieldValue == null)
{ {
_string.reset(); _string.reset();
@ -453,26 +394,26 @@ public class MultiPartParser
setState(FieldState.VALUE); setState(FieldState.VALUE);
break; break;
} }
case LINE_FEED: case LINE_FEED:
{ {
handleField(); handleField();
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())
return true; return true;
break; break;
} }
default: default:
{ {
// process previous header // process previous header
handleField(); handleField();
// New header // New header
setState(FieldState.IN_NAME); setState(FieldState.IN_NAME);
_string.reset(); _string.reset();
@ -481,7 +422,7 @@ public class MultiPartParser
} }
} }
break; break;
case IN_NAME: case IN_NAME:
switch (b) switch (b)
{ {
@ -490,29 +431,29 @@ public class MultiPartParser
_length = -1; _length = -1;
setState(FieldState.VALUE); setState(FieldState.VALUE);
break; break;
case SPACE: case SPACE:
// Ignore trailing whitespaces // Ignore trailing whitespaces
setState(FieldState.AFTER_NAME); setState(FieldState.AFTER_NAME);
break; break;
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();
setState(FieldState.FIELD); setState(FieldState.FIELD);
break; break;
} }
default: default:
_string.append(b); _string.append(b);
_length = _string.length(); _length = _string.length();
break; break;
} }
break; break;
case AFTER_NAME: case AFTER_NAME:
switch (b) switch (b)
{ {
@ -521,22 +462,22 @@ public class MultiPartParser
_length = -1; _length = -1;
setState(FieldState.VALUE); setState(FieldState.VALUE);
break; break;
case LINE_FEED: case LINE_FEED:
_fieldName = takeString(); _fieldName = takeString();
_string.reset(); _string.reset();
_fieldValue = ""; _fieldValue = "";
_length = -1; _length = -1;
break; break;
case SPACE: case SPACE:
break; break;
default: default:
throw new IllegalCharacterException(_state,b,buffer); throw new IllegalCharacterException(_state, b, buffer);
} }
break; break;
case VALUE: case VALUE:
switch (b) switch (b)
{ {
@ -544,14 +485,14 @@ public class MultiPartParser
_string.reset(); _string.reset();
_fieldValue = ""; _fieldValue = "";
_length = -1; _length = -1;
setState(FieldState.FIELD); setState(FieldState.FIELD);
break; break;
case SPACE: case SPACE:
case TAB: case TAB:
break; break;
default: default:
_string.append(b); _string.append(b);
_length = _string.length(); _length = _string.length();
@ -559,14 +500,14 @@ public class MultiPartParser
break; break;
} }
break; break;
case IN_VALUE: case IN_VALUE:
switch (b) switch (b)
{ {
case SPACE: case SPACE:
_string.append(b); _string.append(b);
break; break;
case LINE_FEED: case LINE_FEED:
if (_length > 0) if (_length > 0)
{ {
@ -576,7 +517,7 @@ public class MultiPartParser
} }
setState(FieldState.FIELD); setState(FieldState.FIELD);
break; break;
default: default:
_string.append(b); _string.append(b);
if (b > SPACE || b < 0) if (b > SPACE || b < 0)
@ -584,35 +525,35 @@ public class MultiPartParser
break; break;
} }
break; break;
default: default:
throw new IllegalStateException(_state.toString()); throw new IllegalStateException(_state.toString());
} }
} }
return false; return false;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
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;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
protected boolean parseOctetContent(ByteBuffer buffer) protected boolean parseOctetContent(ByteBuffer buffer)
{ {
// 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,12 +562,12 @@ 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;
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
return false; return false;
@ -642,78 +583,78 @@ public class MultiPartParser
} }
content.limit(_partialBoundary); content.limit(_partialBoundary);
_partialBoundary = 0; _partialBoundary = 0;
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
if (_handler.content(content,false)) if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
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();
content.limit(delimiter - buffer.arrayOffset() - buffer.position()); content.limit(delimiter - buffer.arrayOffset() - buffer.position());
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength()); buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER); setState(State.DELIMITER);
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),true,this);
return _handler.content(content,true); if (LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), true, this);
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;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
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;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
@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,43 +665,45 @@ 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()
{ {
} }
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
@SuppressWarnings("serial") @SuppressWarnings("serial")
private static class IllegalCharacterException extends IllegalArgumentException private static class IllegalCharacterException extends IllegalArgumentException
{ {
private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) private IllegalCharacterException(State state, byte ch, ByteBuffer buffer)
{ {
super(String.format("Illegal character 0x%X",ch)); 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

@ -35,119 +35,127 @@ import org.junit.Test;
public class MultiPartParserTest 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");
data = BufferUtil.toBuffer("This is not part of a part\r\n"); ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE)); parser.parse(data, false);
assertThat(data.remaining(),is(0)); assertThat(parser.getState(), is(State.PREAMBLE));
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,149 +170,136 @@ 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>>"));
} }
@Test @Test
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"));
} }
@Test @Test
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>>"));
} }
@Test @Test
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,15 +331,15 @@ 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>>"));
} }
@Test @Test
public void testBinaryPart() public void testBinaryPart()
@ -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,211 +425,213 @@ 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()
{
@Override public boolean messageComplete(){ return true; }
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
super.content(buffer,last);
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"AaB03x");
ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r"+
"Joe Blow\r\n"+
"--AaB03x--\r\n");
try
{
parser.parse(data,true);
fail("Invalid End of Line");
}
catch(BadMessageException e) {
assertTrue(e.getMessage().contains("Bad EOL"));
}
}
@Test
public void splitTest()
{ {
TestHandler handler = new TestHandler() TestHandler handler = new TestHandler()
{ {
@Override @Override
public boolean messageComplete() public boolean messageComplete()
{ {
return true; 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;
}
};
MultiPartParser parser = new MultiPartParser(handler, "AaB03x");
ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n" +
"\r" +
"Joe Blow\r\n" +
"--AaB03x--\r\n");
try
{
parser.parse(data, true);
fail("Invalid End of Line");
}
catch (BadMessageException e)
{
assertTrue(e.getMessage().contains("Bad EOL"));
}
}
@Test
public void splitTest()
{
TestHandler handler = new TestHandler()
{
@Override
public boolean messageComplete()
{
return true;
}
@Override
public boolean content(ByteBuffer buffer, boolean 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" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" +
"Accept-Language: en-US,en;q=0.5\n" + "Accept-Language: en-US,en;q=0.5\n" +
"Accept-Encoding: gzip, deflate\n" + "Accept-Encoding: gzip, deflate\n" +
"Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" + "Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" +
"Connection: keep-alive\n" + "Connection: keep-alive\n" +
"Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" + "Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" +
"Content-Length: 554\n" + "Content-Length: 554\n" +
"\r\n" + "\r\n" +
"-----------------------------9051914041544843365972754266\n" + "-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"text\"\n" + "Content-Disposition: form-data; name=\"text\"\n" +
"\n" + "\n" +
"text default\r\n" + "text default\r\n" +
"-----------------------------9051914041544843365972754266\n" + "-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" + "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" +
"Content-Type: text/plain\n" + "Content-Type: text/plain\n" +
"\n" + "\n" +
"Content of a.txt.\n" + "Content of a.txt.\n" +
"\r\n" + "\r\n" +
"-----------------------------9051914041544843365972754266\n" + "-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" + "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" +
"Content-Type: text/html\n" + "Content-Type: text/html\n" +
"\n" + "\n" +
"<!DOCTYPE html><title>Content of a.html.</title>\n" + "<!DOCTYPE html><title>Content of a.html.</title>\n" +
"\r\n" + "\r\n" +
"-----------------------------9051914041544843365972754266\n" + "-----------------------------9051914041544843365972754266\n" +
"Field1: value1\n" + "Field1: value1\n" +
"Field2: value2\n" + "Field2: value2\n" +
"Field3: value3\n" + "Field3: value3\n" +
"Field4: value4\n" + "Field4: value4\n" +
"Field5: value5\n" + "Field5: value5\n" +
"Field6: value6\n" + "Field6: value6\n" +
"Field7: value7\n" + "Field7: value7\n" +
"Field8: value8\n" + "Field8: value8\n" +
"Field9: value\n" + "Field9: value\n" +
" 9\n" + " 9\n" +
"\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" +
"offers the best evidence of the lapse of time. I remember\n" + "offers the best evidence of the lapse of time. I remember\n" +
"having been much struck with the evidence of\n" + "having been much struck with the evidence of\n" +
"denudation, when viewing volcanic islands, which\n" + "denudation, when viewing volcanic islands, which\n" +
"have been worn by the waves and pared all round\n" + "have been worn by the waves and pared all round\n" +
"into perpendicular cliffs of one or two thousand feet\n" + "into perpendicular cliffs of one or two thousand feet\n" +
"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" + "the open ocean.\n" +
"\r\n" + "\r\n" +
"-----------------------------9051914041544843365972754266--" + "-----------------------------9051914041544843365972754266--" +
"===== ajlkfja;lkdj;lakjd;lkjf ==== epilogue here ==== kajflajdfl;kjafl;kjl;dkfja ====\n\r\n\r\r\r\n\n\n"); "===== ajlkfja;lkdj;lakjd;lkjf ==== epilogue here ==== kajflajdfl;kjafl;kjl;dkfja ====\n\r\n\r\r\r\n\n\n");
int length = data.remaining();
for(int i = 0; i<length-1; i++){ int length = data.remaining();
//partition 0 to i for (int i = 0; i < length - 1; i++)
ByteBuffer dataSeg = data.slice(); {
dataSeg.position(0); //partition 0 to i
dataSeg.limit(i); ByteBuffer dataSeg = data.slice();
assertThat("First "+i,parser.parse(dataSeg,false),is(false)); dataSeg.position(0);
dataSeg.limit(i);
//partition i assertThat("First " + i, parser.parse(dataSeg, false), is(false));
dataSeg = data.slice();
dataSeg.position(i); //partition i
dataSeg.limit(i+1); dataSeg = data.slice();
assertThat("Second "+i,parser.parse(dataSeg,false),is(false)); dataSeg.position(i);
dataSeg.limit(i + 1);
//partition i to length assertThat("Second " + i, parser.parse(dataSeg, false), is(false));
dataSeg = data.slice();
dataSeg.position(i+1); //partition i to length
dataSeg.limit(length); dataSeg = data.slice();
assertThat("Third "+i,parser.parse(dataSeg,true),is(true)); dataSeg.position(i + 1);
dataSeg.limit(length);
assertThat(handler.fields, Matchers.contains( "Content-Disposition: form-data; name=\"text\"","<<COMPLETE>>" assertThat("Third " + i, parser.parse(dataSeg, true), is(true));
, "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
, "Content-Type: text/plain","<<COMPLETE>>" assertThat(handler.fields, Matchers.contains("Content-Disposition: form-data; name=\"text\"", "<<COMPLETE>>"
, "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"" , "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
, "Content-Type: text/html","<<COMPLETE>>" , "Content-Type: text/plain", "<<COMPLETE>>"
, "Field1: value1", "Field2: value2", "Field3: value3" , "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\""
, "Field4: value4", "Field5: value5", "Field6: value6" , "Content-Type: text/html", "<<COMPLETE>>"
, "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>" , "Field1: value1", "Field2: value2", "Field3: value3"
, "Field1: value1","<<COMPLETE>>")); , "Field4: value4", "Field5: value5", "Field6: value6"
, "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>"
, "Field1: value1", "<<COMPLETE>>"));
assertThat(handler.contentString(), is(new String("text default"+"<<LAST>>"
+ "Content of a.txt.\n"+"<<LAST>>"
+ "<!DOCTYPE html><title>Content of a.html.</title>\n"+"<<LAST>>" assertThat(handler.contentString(), is("text default" + "<<LAST>>"
+ "<<LAST>>" + "Content of a.txt.\n" + "<<LAST>>"
+ "But the amount of denudation which the strata have\n" + + "<!DOCTYPE html><title>Content of a.html.</title>\n" + "<<LAST>>"
"in many places suffered, independently of the rate\n" + + "<<LAST>>"
"of accumulation of the degraded matter, probably\n" + + "But the amount of denudation which the strata have\n" +
"offers the best evidence of the lapse of time. I remember\n" + "in many places suffered, independently of the rate\n" +
"having been much struck with the evidence of\n" + "of accumulation of the degraded matter, probably\n" +
"denudation, when viewing volcanic islands, which\n" + "offers the best evidence of the lapse of time. I remember\n" +
"have been worn by the waves and pared all round\n" + "having been much struck with the evidence of\n" +
"into perpendicular cliffs of one or two thousand feet\n" + "denudation, when viewing volcanic islands, which\n" +
"in height; for the gentle slope of the lava-streams,\n" + "have been worn by the waves and pared all round\n" +
"due to their formerly liquid state, showed at a glance\n" + "into perpendicular cliffs of one or two thousand feet\n" +
"how far the hard, rocky beds had once extended into\n" + "in height; for the gentle slope of the lava-streams,\n" +
"the open ocean.\n"+ "<<LAST>>"))); "due to their formerly liquid state, showed at a glance\n" +
"how far the hard, rocky beds had once extended into\n" +
handler.clear(); "the open ocean.\n" + "<<LAST>>"));
parser.reset();
} handler.clear();
parser.reset();
}
} }
@Test @Test
public void testGeneratedForm() public void testGeneratedForm()
{ {
TestHandler handler = new TestHandler() TestHandler handler = new TestHandler()
{ {
@Override @Override
public boolean messageComplete() public boolean messageComplete()
{ {
return true; 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;
} }
@ -645,24 +642,24 @@ 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" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
"Content-Disposition: form-data; name=\"part1\"\r\n" + "Content-Disposition: form-data; name=\"part1\"\r\n" +
"\n" + "\n" +
"wNfミxVam﾿t\r\n" + "wNfミxVam﾿t\r\n" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" +
"Content-Disposition: form-data; name=\"part2\"\r\n" + "Content-Disposition: form-data; name=\"part2\"\r\n" +
"\r\n" + "\r\n" +
"&ᄈᄎ￙ᅱᅢ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));
} }
@ -670,27 +667,28 @@ public class MultiPartParserTest
{ {
List<String> fields = new ArrayList<>(); List<String> fields = new ArrayList<>();
List<String> content = new ArrayList<>(); List<String> content = new ArrayList<>();
@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();
} }
@Override @Override
public boolean headerComplete() public boolean headerComplete()
{ {
fields.add("<<COMPLETE>>"); fields.add("<<COMPLETE>>");
return false; return false;
} }
@Override @Override
public boolean content(ByteBuffer buffer, boolean last) public boolean content(ByteBuffer buffer, boolean last)
{ {
@ -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,14 +42,13 @@ 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;
import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.OptionsBuilder;
@State(Scope.Benchmark) @State(Scope.Benchmark)
public class MultiPartBenchmark public class MultiPartBenchmark
{ {
public static final int MAX_FILE_SIZE = Integer.MAX_VALUE; public static final int MAX_FILE_SIZE = Integer.MAX_VALUE;
@ -67,13 +60,14 @@ public class MultiPartBenchmark
static File _file; static File _file;
static int _numSections; static int _numSections;
static int _numBytesPerSection; static int _numBytesPerSection;
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
// simple form - 2 fields // simple form - 2 fields
data.add("browser-capture-form1-android-chrome"); data.add("browser-capture-form1-android-chrome");
data.add("browser-capture-form1-android-firefox"); data.add("browser-capture-form1-android-firefox");
@ -83,15 +77,15 @@ public class MultiPartBenchmark
data.add("browser-capture-form1-ios-safari"); data.add("browser-capture-form1-ios-safari");
data.add("browser-capture-form1-msie"); data.add("browser-capture-form1-msie");
data.add("browser-capture-form1-osx-safari"); data.add("browser-capture-form1-osx-safari");
// form submitted as shift-jis // form submitted as shift-jis
data.add("browser-capture-sjis-form-edge"); data.add("browser-capture-sjis-form-edge");
data.add("browser-capture-sjis-form-msie"); data.add("browser-capture-sjis-form-msie");
// form submitted as shift-jis (with HTML5 specific hidden _charset_ field) // form submitted as shift-jis (with HTML5 specific hidden _charset_ field)
data.add("browser-capture-sjis-charset-form-edge"); data.add("browser-capture-sjis-charset-form-edge");
data.add("browser-capture-sjis-charset-form-msie"); data.add("browser-capture-sjis-charset-form-msie");
// form submitted with simple file upload // form submitted with simple file upload
data.add("browser-capture-form-fileupload-android-chrome"); data.add("browser-capture-form-fileupload-android-chrome");
data.add("browser-capture-form-fileupload-android-firefox"); data.add("browser-capture-form-fileupload-android-firefox");
@ -101,7 +95,7 @@ public class MultiPartBenchmark
data.add("browser-capture-form-fileupload-ios-safari"); data.add("browser-capture-form-fileupload-ios-safari");
data.add("browser-capture-form-fileupload-msie"); data.add("browser-capture-form-fileupload-msie");
data.add("browser-capture-form-fileupload-safari"); data.add("browser-capture-form-fileupload-safari");
// form submitted with 2 files (1 binary, 1 text) and 2 text fields // form submitted with 2 files (1 binary, 1 text) and 2 text fields
data.add("browser-capture-form-fileupload-alt-chrome"); data.add("browser-capture-form-fileupload-alt-chrome");
data.add("browser-capture-form-fileupload-alt-edge"); data.add("browser-capture-form-fileupload-alt-edge");
@ -109,19 +103,19 @@ public class MultiPartBenchmark
data.add("browser-capture-form-fileupload-alt-msie"); data.add("browser-capture-form-fileupload-alt-msie");
data.add("browser-capture-form-fileupload-alt-safari"); data.add("browser-capture-form-fileupload-alt-safari");
} }
@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,17 +124,18 @@ 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];
new Random().nextBytes(data); new Random().nextBytes(data);
@ -149,25 +144,9 @@ 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");
*/
} }
@Benchmark @Benchmark
@BenchmarkMode({Mode.AverageTime}) @BenchmarkMode({Mode.AverageTime})
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -175,44 +154,44 @@ public class MultiPartBenchmark
{ {
Path multipartRawFile = _file.toPath(); Path multipartRawFile = _file.toPath();
Path outputDir = new File("/tmp").toPath(); Path outputDir = new File("/tmp").toPath();
MultipartConfigElement config = newMultipartConfigElement(outputDir); MultipartConfigElement config = newMultipartConfigElement(outputDir);
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");
} }
} }
return count; return count;
} }
@TearDown(Level.Trial) @TearDown(Level.Trial)
@ -220,7 +199,7 @@ public class MultiPartBenchmark
{ {
_file = null; _file = null;
} }
private MultipartConfigElement newMultipartConfigElement(Path path) private MultipartConfigElement newMultipartConfigElement(Path path)
{ {
return new MultipartConfigElement(path.toString(), MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD); return new MultipartConfigElement(path.toString(), MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD);
@ -231,48 +210,48 @@ 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");
Path outputDir = new File("/tmp").toPath(); Path outputDir = new File("/tmp").toPath();
MultipartExpectations multipartExpectations = new MultipartExpectations(expectationPath); MultipartExpectations multipartExpectations = new MultipartExpectations(expectationPath);
MultipartConfigElement config = newMultipartConfigElement(outputDir); MultipartConfigElement config = newMultipartConfigElement(outputDir);
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());
for (Part p : parser.getParts())
{ {
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, multipartExpectations.contentType, config, outputDir.toFile()); count += p.getSize();
for(Part p : parser.getParts()) {
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());
for (Part p : parser.getParts())
{ {
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in,multipartExpectations.contentType,config,outputDir.toFile()); count += p.getSize();
// TODO this is using the http version of part (which should be the same anyway)
for(Part p : parser.getParts()) {
count += p.getSize();
}
} }
break; }
break;
default: default:
throw new IllegalStateException("Unknown parserType Parameter"); throw new IllegalStateException("Unknown parserType Parameter");
} }
} }
} }
return count; return count;
} }
public static void main(String[] args) throws RunnerException public static void main(String[] args) throws RunnerException
{ {
Options opt = new OptionsBuilder() Options opt = new OptionsBuilder()
.include(MultiPartBenchmark.class.getSimpleName()) .include(MultiPartBenchmark.class.getSimpleName())
@ -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();