Merge pull request #2476 from lachlan-roberts/jetty-9.4.x-1027-MultiPart-Cleanup
MultiPart Cleanup
This commit is contained in:
commit
c865aa1be7
|
@ -447,7 +447,7 @@ public class HttpParser
|
|||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
enum CharState { ILLEGAL, CR, LF, LEGAL }
|
||||
private final static CharState[] __charState;
|
||||
public final static CharState[] TOKEN_CHAR;
|
||||
static
|
||||
{
|
||||
// token = 1*tchar
|
||||
|
@ -462,38 +462,38 @@ public class HttpParser
|
|||
// 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;
|
||||
TOKEN_CHAR =new CharState[256];
|
||||
Arrays.fill(TOKEN_CHAR,CharState.ILLEGAL);
|
||||
TOKEN_CHAR[LINE_FEED]=CharState.LF;
|
||||
TOKEN_CHAR[CARRIAGE_RETURN]=CharState.CR;
|
||||
TOKEN_CHAR[TAB]=CharState.LEGAL;
|
||||
TOKEN_CHAR[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;
|
||||
TOKEN_CHAR['!']=CharState.LEGAL;
|
||||
TOKEN_CHAR['#']=CharState.LEGAL;
|
||||
TOKEN_CHAR['$']=CharState.LEGAL;
|
||||
TOKEN_CHAR['%']=CharState.LEGAL;
|
||||
TOKEN_CHAR['&']=CharState.LEGAL;
|
||||
TOKEN_CHAR['\'']=CharState.LEGAL;
|
||||
TOKEN_CHAR['*']=CharState.LEGAL;
|
||||
TOKEN_CHAR['+']=CharState.LEGAL;
|
||||
TOKEN_CHAR['-']=CharState.LEGAL;
|
||||
TOKEN_CHAR['.']=CharState.LEGAL;
|
||||
TOKEN_CHAR['^']=CharState.LEGAL;
|
||||
TOKEN_CHAR['_']=CharState.LEGAL;
|
||||
TOKEN_CHAR['`']=CharState.LEGAL;
|
||||
TOKEN_CHAR['|']=CharState.LEGAL;
|
||||
TOKEN_CHAR['~']=CharState.LEGAL;
|
||||
|
||||
__charState['"']=CharState.LEGAL;
|
||||
TOKEN_CHAR['"']=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);
|
||||
TOKEN_CHAR['\\']=CharState.LEGAL;
|
||||
TOKEN_CHAR['(']=CharState.LEGAL;
|
||||
TOKEN_CHAR[')']=CharState.LEGAL;
|
||||
Arrays.fill(TOKEN_CHAR,0x21,0x27+1,CharState.LEGAL);
|
||||
Arrays.fill(TOKEN_CHAR,0x2A,0x5B+1,CharState.LEGAL);
|
||||
Arrays.fill(TOKEN_CHAR,0x5D,0x7E+1,CharState.LEGAL);
|
||||
Arrays.fill(TOKEN_CHAR,0x80,0xFF+1,CharState.LEGAL);
|
||||
|
||||
}
|
||||
|
||||
|
@ -502,7 +502,7 @@ public class HttpParser
|
|||
{
|
||||
byte ch = buffer.get();
|
||||
|
||||
CharState s = __charState[0xff & ch];
|
||||
CharState s = TOKEN_CHAR[0xff & ch];
|
||||
switch(s)
|
||||
{
|
||||
case ILLEGAL:
|
||||
|
|
|
@ -52,28 +52,27 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
|
||||
/**
|
||||
* MultiPartInputStream
|
||||
*
|
||||
* <p>
|
||||
* 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>
|
||||
*/
|
||||
public class MultiPartFormInputStream
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class);
|
||||
private final int _bufferSize = 16 * 1024;
|
||||
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
|
||||
public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
|
||||
protected InputStream _in;
|
||||
protected MultipartConfigElement _config;
|
||||
protected String _contentType;
|
||||
protected MultiMap<Part> _parts;
|
||||
protected Throwable _err;
|
||||
protected File _tmpDir;
|
||||
protected File _contextTmpDir;
|
||||
protected boolean _deleteOnExit;
|
||||
protected boolean _writeFilesWithFilenames;
|
||||
protected boolean _parsed;
|
||||
|
||||
private static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
|
||||
private InputStream _in;
|
||||
private MultipartConfigElement _config;
|
||||
private String _contentType;
|
||||
private MultiMap<Part> _parts;
|
||||
private Throwable _err;
|
||||
private File _tmpDir;
|
||||
private File _contextTmpDir;
|
||||
private boolean _deleteOnExit;
|
||||
private boolean _writeFilesWithFilenames;
|
||||
private boolean _parsed;
|
||||
private int _bufferSize = 16 * 1024;
|
||||
|
||||
public class MultiPart implements Part
|
||||
{
|
||||
protected String _name;
|
||||
|
@ -85,24 +84,24 @@ public class MultiPartFormInputStream
|
|||
protected MultiMap<String> _headers;
|
||||
protected long _size = 0;
|
||||
protected boolean _temporary = true;
|
||||
|
||||
public MultiPart(String name, String filename) throws IOException
|
||||
|
||||
public MultiPart(String name, String filename)
|
||||
{
|
||||
_name = name;
|
||||
_filename = filename;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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)
|
||||
{
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
|
||||
protected void open() throws IOException
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void close() throws IOException
|
||||
{
|
||||
_out.close();
|
||||
}
|
||||
|
||||
|
||||
protected void write(int b) throws IOException
|
||||
{
|
||||
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize())
|
||||
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
|
||||
|
||||
|
||||
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold()
|
||||
&& _file == null)
|
||||
createFile();
|
||||
|
||||
|
||||
_out.write(b);
|
||||
_size++;
|
||||
}
|
||||
|
||||
|
||||
protected void write(byte[] bytes, int offset, int length) throws IOException
|
||||
{
|
||||
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize())
|
||||
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
|
||||
|
||||
|
||||
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0
|
||||
&& _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null)
|
||||
createFile();
|
||||
|
||||
_out.write(bytes,offset,length);
|
||||
|
||||
_out.write(bytes, offset, length);
|
||||
_size += length;
|
||||
}
|
||||
|
||||
|
||||
protected void createFile() throws IOException
|
||||
{
|
||||
/*
|
||||
|
@ -158,16 +157,16 @@ public class MultiPartFormInputStream
|
|||
*/
|
||||
final boolean USER = true;
|
||||
final boolean WORLD = false;
|
||||
|
||||
_file = File.createTempFile("MultiPart","",MultiPartFormInputStream.this._tmpDir);
|
||||
_file.setReadable(false,WORLD); // (reset) disable it for everyone first
|
||||
_file.setReadable(true,USER); // enable for user only
|
||||
|
||||
|
||||
_file = File.createTempFile("MultiPart", "", MultiPartFormInputStream.this._tmpDir);
|
||||
_file.setReadable(false, WORLD); // (reset) disable it for everyone first
|
||||
_file.setReadable(true, USER); // enable for user only
|
||||
|
||||
if (_deleteOnExit)
|
||||
_file.deleteOnExit();
|
||||
FileOutputStream fos = new FileOutputStream(_file);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||
|
||||
|
||||
if (_size > 0 && _out != null)
|
||||
{
|
||||
// already written some bytes, so need to copy them into the file
|
||||
|
@ -178,53 +177,38 @@ public class MultiPartFormInputStream
|
|||
_bout = null;
|
||||
_out = bos;
|
||||
}
|
||||
|
||||
|
||||
protected void setHeaders(MultiMap<String> headers)
|
||||
{
|
||||
_headers = headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getContentType()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String getContentType()
|
||||
{
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getHeader(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
return _headers.getValue(StringUtil.asciiToLowerCase(name),0);
|
||||
return _headers.getValue(StringUtil.asciiToLowerCase(name), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getHeaderNames()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaderNames()
|
||||
{
|
||||
return _headers.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getHeaders(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaders(String name)
|
||||
{
|
||||
return _headers.getValues(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getInputStream()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
|
@ -236,68 +220,52 @@ public class MultiPartFormInputStream
|
|||
else
|
||||
{
|
||||
// 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
|
||||
public String getSubmittedFileName()
|
||||
{
|
||||
return getContentDispositionFilename();
|
||||
}
|
||||
|
||||
|
||||
public byte[] getBytes()
|
||||
{
|
||||
if (_bout != null)
|
||||
return _bout.toByteArray();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getName()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#getSize()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public long getSize()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.Part#write(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void write(String fileName) throws IOException
|
||||
{
|
||||
if (_file == null)
|
||||
{
|
||||
_temporary = false;
|
||||
|
||||
|
||||
// part data is only in the ByteArrayOutputStream and never been written to disk
|
||||
_file = new File(_tmpDir,fileName);
|
||||
|
||||
BufferedOutputStream bos = null;
|
||||
try
|
||||
_file = new File(_tmpDir, fileName);
|
||||
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(_file)))
|
||||
{
|
||||
bos = new BufferedOutputStream(new FileOutputStream(_file));
|
||||
_bout.writeTo(bos);
|
||||
bos.flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bos != null)
|
||||
bos.close();
|
||||
_bout = null;
|
||||
}
|
||||
}
|
||||
|
@ -305,51 +273,50 @@ public class MultiPartFormInputStream
|
|||
{
|
||||
// the part data is already written to a temporary file, just rename it
|
||||
_temporary = false;
|
||||
|
||||
|
||||
Path src = _file.toPath();
|
||||
Path target = src.resolveSibling(fileName);
|
||||
Files.move(src,target,StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(src, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
_file = target.toFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the file, whether or not Part.write() was called on it (ie no longer temporary)
|
||||
*
|
||||
* @see javax.servlet.http.Part#delete()
|
||||
*/
|
||||
@Override
|
||||
public void delete() throws IOException
|
||||
{
|
||||
if (_file != null && _file.exists())
|
||||
_file.delete();
|
||||
if (!_file.delete())
|
||||
throw new IOException("Could Not Delete File");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only remove tmp files.
|
||||
*
|
||||
* @throws IOException
|
||||
* if unable to delete the file
|
||||
* @throws IOException if unable to delete the file
|
||||
*/
|
||||
public void cleanUp() throws IOException
|
||||
{
|
||||
if (_temporary && _file != null && _file.exists())
|
||||
_file.delete();
|
||||
if (!_file.delete())
|
||||
throw new IOException("Could Not Delete File");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the file
|
||||
*
|
||||
*
|
||||
* @return the file, if any, the data has been written to.
|
||||
*/
|
||||
public File getFile()
|
||||
{
|
||||
return _file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the filename from the content-disposition.
|
||||
*
|
||||
*
|
||||
* @return null or the filename
|
||||
*/
|
||||
public String getContentDispositionFilename()
|
||||
|
@ -357,16 +324,12 @@ public class MultiPartFormInputStream
|
|||
return _filename;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param in
|
||||
* Request input stream
|
||||
* @param contentType
|
||||
* Content-Type header
|
||||
* @param config
|
||||
* MultipartConfigElement
|
||||
* @param contextTmpDir
|
||||
* javax.servlet.context.tempdir
|
||||
* @param in Request input stream
|
||||
* @param contentType Content-Type header
|
||||
* @param config MultipartConfigElement
|
||||
* @param contextTmpDir javax.servlet.context.tempdir
|
||||
*/
|
||||
public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
|
||||
{
|
||||
|
@ -375,10 +338,10 @@ public class MultiPartFormInputStream
|
|||
_contextTmpDir = contextTmpDir;
|
||||
if (_contextTmpDir == null)
|
||||
_contextTmpDir = new File(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
|
||||
if (_config == null)
|
||||
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
|
||||
|
||||
|
||||
if (in instanceof ServletInputStream)
|
||||
{
|
||||
if (((ServletInputStream)in).isFinished())
|
||||
|
@ -390,7 +353,7 @@ public class MultiPartFormInputStream
|
|||
}
|
||||
_in = new BufferedInputStream(in);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return whether the list of parsed parts is empty
|
||||
*/
|
||||
|
@ -402,16 +365,16 @@ public class MultiPartFormInputStream
|
|||
Collection<List<Part>> values = _parts.values();
|
||||
for (List<Part> partList : values)
|
||||
{
|
||||
if(partList.size() != 0)
|
||||
if (partList.size() != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the already parsed parts.
|
||||
*
|
||||
*
|
||||
* @return the parts that were parsed
|
||||
*/
|
||||
@Deprecated
|
||||
|
@ -419,25 +382,25 @@ public class MultiPartFormInputStream
|
|||
{
|
||||
if (_parts == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
|
||||
Collection<List<Part>> values = _parts.values();
|
||||
List<Part> parts = new ArrayList<>();
|
||||
for (List<Part> o : values)
|
||||
{
|
||||
List<Part> asList = LazyList.getList(o,false);
|
||||
List<Part> asList = LazyList.getList(o, false);
|
||||
parts.addAll(asList);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete any tmp storage for parts, and clear out the parts list.
|
||||
*/
|
||||
public void deleteParts()
|
||||
{
|
||||
if (!_parsed)
|
||||
return;
|
||||
|
||||
return;
|
||||
|
||||
Collection<Part> parts;
|
||||
try
|
||||
{
|
||||
|
@ -447,8 +410,8 @@ public class MultiPartFormInputStream
|
|||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
MultiException err = new MultiException();
|
||||
|
||||
MultiException err = null;
|
||||
for (Part p : parts)
|
||||
{
|
||||
try
|
||||
|
@ -457,59 +420,58 @@ public class MultiPartFormInputStream
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (err == null)
|
||||
err = new MultiException();
|
||||
err.add(e);
|
||||
}
|
||||
}
|
||||
_parts.clear();
|
||||
|
||||
err.ifExceptionThrowRuntime();
|
||||
|
||||
if (err != null)
|
||||
err.ifExceptionThrowRuntime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse, if necessary, the multipart data and return the list of Parts.
|
||||
*
|
||||
* @return the parts
|
||||
* @throws IOException
|
||||
* if unable to get the parts
|
||||
* @throws IOException if unable to get the parts
|
||||
*/
|
||||
public Collection<Part> getParts() throws IOException
|
||||
{
|
||||
if (!_parsed)
|
||||
parse();
|
||||
throwIfError();
|
||||
|
||||
|
||||
Collection<List<Part>> values = _parts.values();
|
||||
List<Part> parts = new ArrayList<>();
|
||||
for (List<Part> o : values)
|
||||
{
|
||||
List<Part> asList = LazyList.getList(o,false);
|
||||
List<Part> asList = LazyList.getList(o, false);
|
||||
parts.addAll(asList);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the named Part.
|
||||
*
|
||||
* @param name
|
||||
* the part name
|
||||
* @param name the part name
|
||||
* @return the parts
|
||||
* @throws IOException
|
||||
* if unable to get the part
|
||||
* @throws IOException if unable to get the part
|
||||
*/
|
||||
public Part getPart(String name) throws IOException
|
||||
{
|
||||
if(!_parsed)
|
||||
if (!_parsed)
|
||||
parse();
|
||||
throwIfError();
|
||||
return _parts.getValue(name,0);
|
||||
return _parts.getValue(name, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throws an exception if one has been latched.
|
||||
*
|
||||
* @throws IOException
|
||||
* the exception (if present)
|
||||
*
|
||||
* @throws IOException the exception (if present)
|
||||
*/
|
||||
protected void throwIfError() throws IOException
|
||||
{
|
||||
|
@ -523,10 +485,9 @@ public class MultiPartFormInputStream
|
|||
throw new IllegalStateException(_err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse, if necessary, the multipart stream.
|
||||
*
|
||||
*/
|
||||
protected void parse()
|
||||
{
|
||||
|
@ -537,14 +498,13 @@ public class MultiPartFormInputStream
|
|||
|
||||
try
|
||||
{
|
||||
|
||||
// initialize
|
||||
_parts = new MultiMap<>();
|
||||
|
||||
|
||||
// if its not a multipart request, don't parse it
|
||||
if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
|
||||
return;
|
||||
|
||||
|
||||
// sort out the location to which to write the files
|
||||
if (_config.getLocation() == null)
|
||||
_tmpDir = _contextTmpDir;
|
||||
|
@ -556,70 +516,67 @@ public class MultiPartFormInputStream
|
|||
if (f.isAbsolute())
|
||||
_tmpDir = f;
|
||||
else
|
||||
_tmpDir = new File(_contextTmpDir,_config.getLocation());
|
||||
_tmpDir = new File(_contextTmpDir, _config.getLocation());
|
||||
}
|
||||
|
||||
|
||||
if (!_tmpDir.exists())
|
||||
_tmpDir.mkdirs();
|
||||
|
||||
|
||||
String contentTypeBoundary = "";
|
||||
int bstart = _contentType.indexOf("boundary=");
|
||||
if (bstart >= 0)
|
||||
{
|
||||
int bend = _contentType.indexOf(";",bstart);
|
||||
bend = (bend < 0?_contentType.length():bend);
|
||||
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
|
||||
int bend = _contentType.indexOf(";", bstart);
|
||||
bend = (bend < 0 ? _contentType.length() : bend);
|
||||
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart, bend)).trim());
|
||||
}
|
||||
|
||||
|
||||
Handler handler = new Handler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
|
||||
|
||||
// Create a buffer to store data from stream //
|
||||
MultiPartParser parser = new MultiPartParser(handler, contentTypeBoundary);
|
||||
|
||||
byte[] data = new byte[_bufferSize];
|
||||
int len = 0;
|
||||
|
||||
/*
|
||||
* keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
|
||||
*/
|
||||
int len;
|
||||
long total = 0;
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
|
||||
len = _in.read(data);
|
||||
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
|
||||
// keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
|
||||
total += len;
|
||||
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
|
||||
{
|
||||
_err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(data);
|
||||
buffer.limit(len);
|
||||
if (parser.parse(buffer,false))
|
||||
if (parser.parse(buffer, false))
|
||||
break;
|
||||
|
||||
if(buffer.hasRemaining())
|
||||
if (buffer.hasRemaining())
|
||||
throw new IllegalStateException("Buffer did not fully consume");
|
||||
|
||||
|
||||
}
|
||||
else if (len == -1)
|
||||
{
|
||||
parser.parse(BufferUtil.EMPTY_BUFFER,true);
|
||||
parser.parse(BufferUtil.EMPTY_BUFFER, true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// check for exceptions
|
||||
if (_err != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// check we read to the end of the message
|
||||
if (parser.getState() != MultiPartParser.State.END)
|
||||
{
|
||||
|
@ -628,39 +585,37 @@ public class MultiPartFormInputStream
|
|||
else
|
||||
_err = new IOException("Incomplete Multipart");
|
||||
}
|
||||
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Parsing Complete {} err={}",parser,_err);
|
||||
LOG.debug("Parsing Complete {} err={}", parser, _err);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
_err = e;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Handler implements MultiPartParser.Handler
|
||||
{
|
||||
private MultiPart _part = null;
|
||||
private String contentDisposition = null;
|
||||
private String contentType = null;
|
||||
private MultiMap<String> headers = new MultiMap<>();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parsedField(String key, String value)
|
||||
{
|
||||
{
|
||||
// Add to headers and mark if one of these fields. //
|
||||
headers.put(StringUtil.asciiToLowerCase(key),value);
|
||||
headers.put(StringUtil.asciiToLowerCase(key), value);
|
||||
if (key.equalsIgnoreCase("content-disposition"))
|
||||
contentDisposition = value;
|
||||
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
|
||||
// https://tools.ietf.org/html/rfc7578#section-4.7
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean headerComplete()
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("headerComplete {}",this);
|
||||
LOG.debug("headerComplete {}", this);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
// Extract content-disposition
|
||||
boolean form_data = false;
|
||||
if (contentDisposition == null)
|
||||
{
|
||||
throw new IOException("Missing content-disposition");
|
||||
}
|
||||
|
||||
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true);
|
||||
|
||||
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition, ";", false, true);
|
||||
String name = null;
|
||||
String filename = null;
|
||||
while (tok.hasMoreTokens())
|
||||
|
@ -702,7 +657,7 @@ public class MultiPartFormInputStream
|
|||
else if (tl.startsWith("filename="))
|
||||
filename = filenameValue(t);
|
||||
}
|
||||
|
||||
|
||||
// Check disposition
|
||||
if (!form_data)
|
||||
throw new IOException("Part not form-data");
|
||||
|
@ -714,13 +669,13 @@ public class MultiPartFormInputStream
|
|||
// have not yet seen a name field.
|
||||
if (name == null)
|
||||
throw new IOException("No name in part");
|
||||
|
||||
|
||||
|
||||
|
||||
// create the new part
|
||||
_part = new MultiPart(name,filename);
|
||||
_part = new MultiPart(name, filename);
|
||||
_part.setHeaders(headers);
|
||||
_part.setContentType(contentType);
|
||||
_parts.add(name,_part);
|
||||
_parts.add(name, _part);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -737,21 +692,21 @@ public class MultiPartFormInputStream
|
|||
_err = e;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean content(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
if(_part == null)
|
||||
{
|
||||
if (_part == null)
|
||||
return false;
|
||||
|
||||
if (BufferUtil.hasContent(buffer))
|
||||
{
|
||||
try
|
||||
{
|
||||
_part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
|
||||
_part.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -759,7 +714,7 @@ public class MultiPartFormInputStream
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (last)
|
||||
{
|
||||
try
|
||||
|
@ -772,12 +727,12 @@ public class MultiPartFormInputStream
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void startPart()
|
||||
public void startPart()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
@ -786,9 +741,9 @@ public class MultiPartFormInputStream
|
|||
public void earlyEOF()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Early EOF {}",MultiPartFormInputStream.this);
|
||||
LOG.debug("Early EOF {}", MultiPartFormInputStream.this);
|
||||
}
|
||||
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_part = null;
|
||||
|
@ -797,41 +752,41 @@ public class MultiPartFormInputStream
|
|||
headers = new MultiMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setDeleteOnExit(boolean deleteOnExit)
|
||||
{
|
||||
_deleteOnExit = deleteOnExit;
|
||||
}
|
||||
|
||||
|
||||
public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames)
|
||||
{
|
||||
_writeFilesWithFilenames = writeFilesWithFilenames;
|
||||
}
|
||||
|
||||
|
||||
public boolean isWriteFilesWithFilenames()
|
||||
{
|
||||
return _writeFilesWithFilenames;
|
||||
}
|
||||
|
||||
|
||||
public boolean isDeleteOnExit()
|
||||
{
|
||||
return _deleteOnExit;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private String value(String nameEqualsValue)
|
||||
private static String value(String nameEqualsValue)
|
||||
{
|
||||
int idx = nameEqualsValue.indexOf('=');
|
||||
String value = nameEqualsValue.substring(idx + 1).trim();
|
||||
return QuotedStringTokenizer.unquoteOnly(value);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private String filenameValue(String nameEqualsValue)
|
||||
private static String filenameValue(String nameEqualsValue)
|
||||
{
|
||||
int idx = nameEqualsValue.indexOf('=');
|
||||
String value = nameEqualsValue.substring(idx + 1).trim();
|
||||
|
||||
|
||||
if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
|
||||
{
|
||||
// incorrectly escaped IE filenames that have the whole path
|
||||
|
@ -841,8 +796,8 @@ public class MultiPartFormInputStream
|
|||
value = value.substring(1);
|
||||
char last = value.charAt(value.length() - 1);
|
||||
if (last == '"' || last == '\'')
|
||||
value = value.substring(0,value.length() - 1);
|
||||
|
||||
value = value.substring(0, value.length() - 1);
|
||||
|
||||
return value;
|
||||
}
|
||||
else
|
||||
|
@ -850,7 +805,22 @@ public class MultiPartFormInputStream
|
|||
// form a valid escape sequence to remain as many browsers
|
||||
// even on *nix systems will not escape a filename containing
|
||||
// backslashes
|
||||
return QuotedStringTokenizer.unquoteOnly(value,true);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.http;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
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;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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/rfc2045">https://tools.ietf.org/html/rfc2045</a>
|
||||
*/
|
||||
public class MultiPartParser
|
||||
{
|
||||
public static final Logger LOG = Log.getLogger(MultiPartParser.class);
|
||||
|
||||
static final byte COLON = (byte)':';
|
||||
static final byte TAB = 0x09;
|
||||
static final byte LINE_FEED = 0x0A;
|
||||
static final byte CARRIAGE_RETURN = 0x0D;
|
||||
static final byte SPACE = 0x20;
|
||||
static final byte[] CRLF =
|
||||
{ CARRIAGE_RETURN, LINE_FEED };
|
||||
static final byte SEMI_COLON = (byte)';';
|
||||
|
||||
|
||||
private static final byte COLON = (byte)':';
|
||||
private static final byte TAB = 0x09;
|
||||
private static final byte LINE_FEED = 0x0A;
|
||||
private static final byte CARRIAGE_RETURN = 0x0D;
|
||||
private static final byte SPACE = 0x20;
|
||||
|
||||
// States
|
||||
public enum FieldState
|
||||
{
|
||||
FIELD,
|
||||
IN_NAME,
|
||||
AFTER_NAME,
|
||||
AFTER_NAME,
|
||||
VALUE,
|
||||
IN_VALUE
|
||||
}
|
||||
|
||||
|
||||
// States
|
||||
public enum State
|
||||
{
|
||||
PREAMBLE,
|
||||
DELIMITER,
|
||||
DELIMITER_PADDING,
|
||||
DELIMITER_PADDING,
|
||||
DELIMITER_CLOSE,
|
||||
BODY_PART,
|
||||
FIRST_OCTETS,
|
||||
FIRST_OCTETS,
|
||||
OCTETS,
|
||||
EPILOGUE,
|
||||
EPILOGUE,
|
||||
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 Handler _handler;
|
||||
private final SearchPattern _delimiterSearch;
|
||||
|
||||
|
||||
private String _fieldName;
|
||||
private String _fieldValue;
|
||||
|
||||
|
||||
private State _state = State.PREAMBLE;
|
||||
private FieldState _fieldState = FieldState.FIELD;
|
||||
private int _partialBoundary = 2; // No CRLF if no preamble
|
||||
private boolean _cr;
|
||||
private ByteBuffer _patternBuffer;
|
||||
|
||||
|
||||
private final Utf8StringBuilder _string = new Utf8StringBuilder();
|
||||
private int _length;
|
||||
|
||||
|
||||
private int _totalHeaderLineLength = -1;
|
||||
private int _maxHeaderLineLength = 998;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public MultiPartParser(Handler handler, String boundary)
|
||||
{
|
||||
_handler = handler;
|
||||
|
||||
|
||||
String delimiter = "\r\n--" + boundary;
|
||||
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
|
||||
_delimiterSearch = SearchPattern.compile(_patternBuffer.array());
|
||||
}
|
||||
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_state = State.PREAMBLE;
|
||||
_fieldState = FieldState.FIELD;
|
||||
_partialBoundary = 2; // No CRLF if no preamble
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public Handler getHandler()
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public State getState()
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public boolean isState(State state)
|
||||
{
|
||||
return _state == state;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
enum CharState
|
||||
{
|
||||
ILLEGAL, CR, LF, LEGAL
|
||||
}
|
||||
private final static CharState[] __charState;
|
||||
static
|
||||
{
|
||||
// token = 1*tchar
|
||||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
||||
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
||||
// / DIGIT / ALPHA
|
||||
// ; any VCHAR, except delimiters
|
||||
// quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
||||
// qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
|
||||
// obs-text = %x80-FF
|
||||
// comment = "(" *( ctext / quoted-pair / comment ) ")"
|
||||
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
|
||||
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
|
||||
__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)
|
||||
private static boolean hasNextByte(ByteBuffer buffer)
|
||||
{
|
||||
return BufferUtil.hasContent(buffer);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private byte getNextByte(ByteBuffer buffer)
|
||||
{
|
||||
|
||||
|
||||
byte ch = buffer.get();
|
||||
|
||||
CharState s = __charState[0xff & ch];
|
||||
|
||||
HttpParser.CharState s = HttpParser.TOKEN_CHAR[0xff & ch];
|
||||
switch (s)
|
||||
{
|
||||
case LF:
|
||||
_cr = false;
|
||||
return ch;
|
||||
|
||||
|
||||
case CR:
|
||||
if (_cr)
|
||||
throw new BadMessageException("Bad EOL");
|
||||
|
||||
|
||||
_cr = true;
|
||||
if (buffer.hasRemaining())
|
||||
return getNextByte(buffer);
|
||||
|
||||
|
||||
// Can return 0 here to indicate the need for more characters,
|
||||
// because a real 0 in the buffer would cause a BadMessage below
|
||||
return 0;
|
||||
|
||||
|
||||
case LEGAL:
|
||||
if (_cr)
|
||||
throw new BadMessageException("Bad EOL");
|
||||
|
||||
|
||||
return ch;
|
||||
|
||||
|
||||
case ILLEGAL:
|
||||
default:
|
||||
throw new IllegalCharacterException(_state,ch,buffer);
|
||||
throw new IllegalCharacterException(_state, ch, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void setString(String s)
|
||||
{
|
||||
|
@ -234,7 +177,7 @@ public class MultiPartParser
|
|||
_string.append(s);
|
||||
_length = s.length();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
/*
|
||||
* 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();
|
||||
// trim trailing whitespace.
|
||||
if (s.length()>_length)
|
||||
s = s.substring(0,_length);
|
||||
if (s.length() > _length)
|
||||
s = s.substring(0, _length);
|
||||
_string.reset();
|
||||
_length = -1;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Parse until next Event.
|
||||
*
|
||||
*
|
||||
* @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;
|
||||
*/
|
||||
public boolean parse(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
boolean handle = false;
|
||||
while (handle == false && BufferUtil.hasContent(buffer))
|
||||
while (!handle && BufferUtil.hasContent(buffer))
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case PREAMBLE:
|
||||
parsePreamble(buffer);
|
||||
continue;
|
||||
|
||||
|
||||
case DELIMITER:
|
||||
case DELIMITER_PADDING:
|
||||
case DELIMITER_CLOSE:
|
||||
parseDelimiter(buffer);
|
||||
continue;
|
||||
|
||||
|
||||
case BODY_PART:
|
||||
handle = parseMimePartHeaders(buffer);
|
||||
break;
|
||||
|
||||
|
||||
case FIRST_OCTETS:
|
||||
case OCTETS:
|
||||
handle = parseOctetContent(buffer);
|
||||
break;
|
||||
|
||||
|
||||
case EPILOGUE:
|
||||
BufferUtil.clear(buffer);
|
||||
break;
|
||||
|
||||
|
||||
case END:
|
||||
handle = true;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (last && BufferUtil.isEmpty(buffer))
|
||||
{
|
||||
if (_state == State.EPILOGUE)
|
||||
{
|
||||
_state = State.END;
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("messageComplete {}", this);
|
||||
|
||||
return _handler.messageComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("earlyEOF {}", this);
|
||||
|
||||
_handler.earlyEOF();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void parsePreamble(ByteBuffer buffer)
|
||||
{
|
||||
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 == _delimiterSearch.getLength())
|
||||
|
@ -337,29 +281,27 @@ public class MultiPartParser
|
|||
setState(State.DELIMITER);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_partialBoundary = partial;
|
||||
BufferUtil.clear(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_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)
|
||||
{
|
||||
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
|
||||
setState(State.DELIMITER);
|
||||
return;
|
||||
}
|
||||
|
||||
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
|
||||
|
||||
_partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
|
||||
BufferUtil.clear(buffer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void parseDelimiter(ByteBuffer buffer)
|
||||
{
|
||||
|
@ -368,18 +310,18 @@ public class MultiPartParser
|
|||
byte b = getNextByte(buffer);
|
||||
if (b == 0)
|
||||
return;
|
||||
|
||||
|
||||
if (b == '\n')
|
||||
{
|
||||
setState(State.BODY_PART);
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("startPart {}",this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("startPart {}", this);
|
||||
|
||||
_handler.startPart();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case DELIMITER:
|
||||
|
@ -388,7 +330,7 @@ public class MultiPartParser
|
|||
else
|
||||
setState(State.DELIMITER_PADDING);
|
||||
continue;
|
||||
|
||||
|
||||
case DELIMITER_CLOSE:
|
||||
if (b == '-')
|
||||
{
|
||||
|
@ -397,14 +339,13 @@ public class MultiPartParser
|
|||
}
|
||||
setState(State.DELIMITER_PADDING);
|
||||
continue;
|
||||
|
||||
|
||||
case DELIMITER_PADDING:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
/*
|
||||
* 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);
|
||||
if (b == 0)
|
||||
break;
|
||||
|
||||
|
||||
if (b != LINE_FEED)
|
||||
_totalHeaderLineLength++;
|
||||
|
||||
if (_totalHeaderLineLength > _maxHeaderLineLength)
|
||||
|
||||
if (_totalHeaderLineLength > MAX_HEADER_LINE_LENGTH)
|
||||
throw new IllegalStateException("Header Line Exceeded Max Length");
|
||||
|
||||
|
||||
switch (_fieldState)
|
||||
{
|
||||
case FIELD:
|
||||
|
@ -434,10 +375,10 @@ public class MultiPartParser
|
|||
case TAB:
|
||||
{
|
||||
// Folded field value!
|
||||
|
||||
|
||||
if (_fieldName == null)
|
||||
throw new IllegalStateException("First field folded");
|
||||
|
||||
|
||||
if (_fieldValue == null)
|
||||
{
|
||||
_string.reset();
|
||||
|
@ -453,26 +394,26 @@ public class MultiPartParser
|
|||
setState(FieldState.VALUE);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case LINE_FEED:
|
||||
{
|
||||
handleField();
|
||||
setState(State.FIRST_OCTETS);
|
||||
_partialBoundary = 2; // CRLF is option for empty parts
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("headerComplete {}", this);
|
||||
|
||||
if (_handler.headerComplete())
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
{
|
||||
// process previous header
|
||||
handleField();
|
||||
|
||||
|
||||
// New header
|
||||
setState(FieldState.IN_NAME);
|
||||
_string.reset();
|
||||
|
@ -481,7 +422,7 @@ public class MultiPartParser
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case IN_NAME:
|
||||
switch (b)
|
||||
{
|
||||
|
@ -490,29 +431,29 @@ public class MultiPartParser
|
|||
_length = -1;
|
||||
setState(FieldState.VALUE);
|
||||
break;
|
||||
|
||||
|
||||
case SPACE:
|
||||
// Ignore trailing whitespaces
|
||||
setState(FieldState.AFTER_NAME);
|
||||
break;
|
||||
|
||||
|
||||
case LINE_FEED:
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Line Feed in Name {}", this);
|
||||
|
||||
handleField();
|
||||
setState(FieldState.FIELD);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
_string.append(b);
|
||||
_length = _string.length();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case AFTER_NAME:
|
||||
switch (b)
|
||||
{
|
||||
|
@ -521,22 +462,22 @@ public class MultiPartParser
|
|||
_length = -1;
|
||||
setState(FieldState.VALUE);
|
||||
break;
|
||||
|
||||
|
||||
case LINE_FEED:
|
||||
_fieldName = takeString();
|
||||
_string.reset();
|
||||
_fieldValue = "";
|
||||
_length = -1;
|
||||
break;
|
||||
|
||||
|
||||
case SPACE:
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new IllegalCharacterException(_state,b,buffer);
|
||||
throw new IllegalCharacterException(_state, b, buffer);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case VALUE:
|
||||
switch (b)
|
||||
{
|
||||
|
@ -544,14 +485,14 @@ public class MultiPartParser
|
|||
_string.reset();
|
||||
_fieldValue = "";
|
||||
_length = -1;
|
||||
|
||||
|
||||
setState(FieldState.FIELD);
|
||||
break;
|
||||
|
||||
|
||||
case SPACE:
|
||||
case TAB:
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
_string.append(b);
|
||||
_length = _string.length();
|
||||
|
@ -559,14 +500,14 @@ public class MultiPartParser
|
|||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case IN_VALUE:
|
||||
switch (b)
|
||||
{
|
||||
case SPACE:
|
||||
_string.append(b);
|
||||
break;
|
||||
|
||||
|
||||
case LINE_FEED:
|
||||
if (_length > 0)
|
||||
{
|
||||
|
@ -576,7 +517,7 @@ public class MultiPartParser
|
|||
}
|
||||
setState(FieldState.FIELD);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
_string.append(b);
|
||||
if (b > SPACE || b < 0)
|
||||
|
@ -584,35 +525,35 @@ public class MultiPartParser
|
|||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(_state.toString());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void handleField()
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this);
|
||||
|
||||
if (_fieldName != null && _fieldValue != null)
|
||||
_handler.parsedField(_fieldName,_fieldValue);
|
||||
_handler.parsedField(_fieldName, _fieldValue);
|
||||
_fieldName = _fieldValue = null;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
protected boolean parseOctetContent(ByteBuffer buffer)
|
||||
{
|
||||
|
||||
|
||||
// Starts With
|
||||
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 == _delimiterSearch.getLength())
|
||||
|
@ -621,12 +562,12 @@ public class MultiPartParser
|
|||
setState(State.DELIMITER);
|
||||
_partialBoundary = 0;
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER),true,this);
|
||||
if (LOG.isDebugEnabled())
|
||||
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;
|
||||
BufferUtil.clear(buffer);
|
||||
return false;
|
||||
|
@ -642,78 +583,78 @@ public class MultiPartParser
|
|||
}
|
||||
content.limit(_partialBoundary);
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
ByteBuffer content = buffer.slice();
|
||||
content.limit(delimiter - buffer.arrayOffset() - buffer.position());
|
||||
|
||||
|
||||
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
|
||||
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
|
||||
_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)
|
||||
{
|
||||
ByteBuffer content = buffer.slice();
|
||||
content.limit(content.limit() - _partialBoundary);
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
|
||||
|
||||
BufferUtil.clear(buffer);
|
||||
return _handler.content(content,false);
|
||||
return _handler.content(content, false);
|
||||
}
|
||||
|
||||
|
||||
// There is normal content with no delimiter
|
||||
ByteBuffer content = buffer.slice();
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
|
||||
|
||||
BufferUtil.clear(buffer);
|
||||
return _handler.content(content,false);
|
||||
return _handler.content(content, false);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void setState(State state)
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{} --> {}",_state,state);
|
||||
LOG.debug("{} --> {}", _state, state);
|
||||
_state = state;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private void setState(FieldState state)
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{}:{} --> {}",_state,_fieldState,state);
|
||||
LOG.debug("{}:{} --> {}", _state, _fieldState, state);
|
||||
_fieldState = state;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s{s=%s}",getClass().getSimpleName(),_state);
|
||||
return String.format("%s{s=%s}", getClass().getSimpleName(), _state);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -724,43 +665,45 @@ public class MultiPartParser
|
|||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
public default boolean content(ByteBuffer item, boolean last)
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
default boolean content(ByteBuffer item, boolean last)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public default boolean messageComplete()
|
||||
|
||||
default boolean messageComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public default void earlyEOF()
|
||||
|
||||
default void earlyEOF()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
@SuppressWarnings("serial")
|
||||
private static class IllegalCharacterException extends IllegalArgumentException
|
||||
{
|
||||
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
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -35,119 +35,127 @@ import org.junit.Test;
|
|||
|
||||
public class MultiPartParserTest
|
||||
{
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyPreamble()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "BOUNDARY");
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoPreamble()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "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));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreamble()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "BOUNDARY");
|
||||
ByteBuffer data;
|
||||
|
||||
data = BufferUtil.toBuffer("This is not part of a part\r\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
|
||||
data = BufferUtil.toBuffer("More data that almost includes \n--BOUNDARY but no CR before.");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
|
||||
data = BufferUtil.toBuffer("Could be a boundary \r\n--BOUNDAR");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
|
||||
data = BufferUtil.toBuffer("but not it isn't \r\n--BOUN");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
|
||||
data = BufferUtil.toBuffer("DARX nor is this");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPreambleCompleteBoundary()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
ByteBuffer data;
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "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);
|
||||
assertThat(parser.getState(),is(State.BODY_PART));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.BODY_PART));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreambleSplitBoundary()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
ByteBuffer data;
|
||||
|
||||
data = BufferUtil.toBuffer("This is not part of a part\r\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "BOUNDARY");
|
||||
ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n");
|
||||
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("-");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("-");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("B");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.PREAMBLE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.PREAMBLE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("OUNDARY-");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.DELIMITER_CLOSE));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER_CLOSE));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("ignore\r");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.DELIMITER_PADDING));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER_PADDING));
|
||||
assertThat(data.remaining(), is(0));
|
||||
data = BufferUtil.toBuffer("\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.BODY_PART));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.BODY_PART));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstPartNoFields()
|
||||
{
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
|
||||
{
|
||||
}, "BOUNDARY");
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.FIRST_OCTETS));
|
||||
assertThat(data.remaining(),is(0));
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.FIRST_OCTETS));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -162,149 +170,136 @@ public class MultiPartParserTest
|
|||
return true;
|
||||
}
|
||||
};
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name0: value0\r\n"
|
||||
+ "name1 :value1 \r\n"
|
||||
+ "name2:value\r\n"
|
||||
+ " 2\r\n"
|
||||
+ "\r\n"
|
||||
+ "Content");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.FIRST_OCTETS));
|
||||
assertThat(data.remaining(),is(7));
|
||||
assertThat(handler.fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<<COMPLETE>>"));
|
||||
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.FIRST_OCTETS));
|
||||
assertThat(data.remaining(), is(7));
|
||||
assertThat(handler.fields, Matchers.contains("name0: value0", "name1: value1", "name2: value 2", "<<COMPLETE>>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstPartNoContent()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\r\n"
|
||||
+ "\r\n"
|
||||
+ "\r\n"
|
||||
+ "--BOUNDARY");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("<<LAST>>"));
|
||||
}
|
||||
assertThat(data.remaining(), is(0));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("<<LAST>>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstPartNoContentNoCRLF()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\r\n"
|
||||
+ "\r\n"
|
||||
+ "--BOUNDARY");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("<<LAST>>"));
|
||||
assertThat(data.remaining(), is(0));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("<<LAST>>"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstPartContentLookingLikeNoCRLF()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\r\n"
|
||||
+ "\r\n"
|
||||
+ "-");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
data = BufferUtil.toBuffer("Content!");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
|
||||
|
||||
assertThat(parser.getState(), is(State.OCTETS));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("-","Content!"));
|
||||
assertThat(data.remaining(), is(0));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("-", "Content!"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstPartPartialContent()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\n"
|
||||
+ "\r\n"
|
||||
+ "Hello\r\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.OCTETS));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("Hello"));
|
||||
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.OCTETS));
|
||||
assertThat(data.remaining(), is(0));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("Hello"));
|
||||
|
||||
data = BufferUtil.toBuffer(
|
||||
"Now is the time for all good ment to come to the aid of the party.\r\n"
|
||||
+ "How now brown cow.\r\n"
|
||||
+ "The quick brown fox jumped over the lazy dog.\r\n"
|
||||
+ "this is not a --BOUNDARY\r\n");
|
||||
parser.parse(data,false);
|
||||
assertThat(parser.getState(),is(State.OCTETS));
|
||||
assertThat(data.remaining(),is(0));
|
||||
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"
|
||||
+ "How now brown cow.\r\n"
|
||||
+ "The quick brown fox jumped over the lazy dog.\r\n"
|
||||
+ "this is not a --BOUNDARY\r\n");
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.OCTETS));
|
||||
assertThat(data.remaining(), is(0));
|
||||
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"
|
||||
+ "How now brown cow.\r\n"
|
||||
+ "The quick brown fox jumped over the lazy dog.\r\n"
|
||||
+ "this is not a --BOUNDARY"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstPartShortContent()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\n"
|
||||
+ "\r\n"
|
||||
+ "Hello\r\n"
|
||||
+ "--BOUNDARY");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>"));
|
||||
assertThat(data.remaining(), is(0));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstPartLongContent()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n"
|
||||
+ "name: value\n"
|
||||
+ "\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"
|
||||
+ "\r\n"
|
||||
+ "--BOUNDARY");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(0));
|
||||
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(data.remaining(), is(0));
|
||||
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"
|
||||
+ "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
|
||||
public void testFirstPartLongContentNoCarriageReturn()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer("");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
//boundary still requires carriage return
|
||||
data = BufferUtil.toBuffer("--BOUNDARY\n"
|
||||
ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\n"
|
||||
+ "name: value\n"
|
||||
+ "\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"
|
||||
+ "\r\n"
|
||||
+ "--BOUNDARY");
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(0));
|
||||
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(data.remaining(), is(0));
|
||||
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"
|
||||
+ "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
|
||||
public void testBinaryPart()
|
||||
|
@ -361,30 +354,31 @@ public class MultiPartParserTest
|
|||
@Override
|
||||
public boolean content(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
BufferUtil.append(bytes,buffer);
|
||||
BufferUtil.append(bytes, buffer);
|
||||
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 epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n";
|
||||
|
||||
ByteBuffer data = BufferUtil.allocate(preamble.length()+random.length+epilogue.length());
|
||||
BufferUtil.append(data,BufferUtil.toBuffer(preamble));
|
||||
BufferUtil.append(data,ByteBuffer.wrap(random));
|
||||
BufferUtil.append(data,BufferUtil.toBuffer(epilogue));
|
||||
ByteBuffer data = BufferUtil.allocate(preamble.length() + random.length + epilogue.length());
|
||||
BufferUtil.append(data, BufferUtil.toBuffer(preamble));
|
||||
BufferUtil.append(data, ByteBuffer.wrap(random));
|
||||
BufferUtil.append(data, BufferUtil.toBuffer(epilogue));
|
||||
|
||||
parser.parse(data,true);
|
||||
parser.parse(data, true);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(data.remaining(),is(19));
|
||||
assertThat(bytes.array(),is(random));
|
||||
assertThat(data.remaining(), is(19));
|
||||
assertThat(bytes.array(), is(random));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEpilogue() {
|
||||
public void testEpilogue()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer(""
|
||||
+ "--BOUNDARY\r\n"
|
||||
|
@ -399,20 +393,21 @@ public class MultiPartParserTest
|
|||
+ "--BOUNDARY");
|
||||
|
||||
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>"));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>"));
|
||||
|
||||
parser.parse(data,true);
|
||||
parser.parse(data, true);
|
||||
assertThat(parser.getState(), is(State.END));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleContent() {
|
||||
public void testMultipleContent()
|
||||
{
|
||||
TestHandler handler = new TestHandler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
|
||||
MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY");
|
||||
|
||||
ByteBuffer data = BufferUtil.toBuffer(""
|
||||
+ "--BOUNDARY\r\n"
|
||||
|
@ -430,211 +425,213 @@ public class MultiPartParserTest
|
|||
+ "epilogue here");
|
||||
|
||||
/* Test First Content Section */
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
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 */
|
||||
parser.parse(data,false);
|
||||
parser.parse(data, false);
|
||||
assertThat(parser.getState(), is(State.DELIMITER));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>","powerLevel: 9001","<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>","secondary\r\ncontent","<<LAST>>"));
|
||||
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>", "powerLevel: 9001", "<<COMPLETE>>"));
|
||||
assertThat(handler.content, Matchers.contains("Hello", "<<LAST>>", "secondary\r\ncontent", "<<LAST>>"));
|
||||
|
||||
/* Test Progression to END State */
|
||||
parser.parse(data,true);
|
||||
parser.parse(data, true);
|
||||
assertThat(parser.getState(), is(State.END));
|
||||
assertThat(data.remaining(),is(0));
|
||||
assertThat(data.remaining(), is(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
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()
|
||||
public void testCrAsLineTermination()
|
||||
{
|
||||
TestHandler handler = new TestHandler()
|
||||
TestHandler handler = new TestHandler()
|
||||
{
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
MultiPartParser parser = new MultiPartParser(handler,"---------------------------9051914041544843365972754266");
|
||||
ByteBuffer data = BufferUtil.toBuffer(""+
|
||||
"POST / HTTP/1.1\n" +
|
||||
"Host: localhost:8000\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-Language: en-US,en;q=0.5\n" +
|
||||
"Accept-Encoding: gzip, deflate\n" +
|
||||
"Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" +
|
||||
"Connection: keep-alive\n" +
|
||||
"Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" +
|
||||
"Content-Length: 554\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"text\"\n" +
|
||||
"\n" +
|
||||
"text default\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" +
|
||||
"Content-Type: text/plain\n" +
|
||||
"\n" +
|
||||
"Content of a.txt.\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" +
|
||||
"Content-Type: text/html\n" +
|
||||
"\n" +
|
||||
"<!DOCTYPE html><title>Content of a.html.</title>\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Field1: value1\n" +
|
||||
"Field2: value2\n" +
|
||||
"Field3: value3\n" +
|
||||
"Field4: value4\n" +
|
||||
"Field5: value5\n" +
|
||||
MultiPartParser parser = new MultiPartParser(handler, "---------------------------9051914041544843365972754266");
|
||||
ByteBuffer data = BufferUtil.toBuffer("" +
|
||||
"POST / HTTP/1.1\n" +
|
||||
"Host: localhost:8000\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-Language: en-US,en;q=0.5\n" +
|
||||
"Accept-Encoding: gzip, deflate\n" +
|
||||
"Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" +
|
||||
"Connection: keep-alive\n" +
|
||||
"Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" +
|
||||
"Content-Length: 554\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"text\"\n" +
|
||||
"\n" +
|
||||
"text default\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" +
|
||||
"Content-Type: text/plain\n" +
|
||||
"\n" +
|
||||
"Content of a.txt.\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" +
|
||||
"Content-Type: text/html\n" +
|
||||
"\n" +
|
||||
"<!DOCTYPE html><title>Content of a.html.</title>\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Field1: value1\n" +
|
||||
"Field2: value2\n" +
|
||||
"Field3: value3\n" +
|
||||
"Field4: value4\n" +
|
||||
"Field5: value5\n" +
|
||||
"Field6: value6\n" +
|
||||
"Field7: value7\n" +
|
||||
"Field8: value8\n" +
|
||||
"Field9: value\n" +
|
||||
"Field9: value\n" +
|
||||
" 9\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Field1: value1\n" +
|
||||
"\r\n"+
|
||||
"But the amount of denudation which the strata have\n" +
|
||||
"in many places suffered, independently of the rate\n" +
|
||||
"of accumulation of the degraded matter, probably\n" +
|
||||
"offers the best evidence of the lapse of time. I remember\n" +
|
||||
"having been much struck with the evidence of\n" +
|
||||
"denudation, when viewing volcanic islands, which\n" +
|
||||
"have been worn by the waves and pared all round\n" +
|
||||
"into perpendicular cliffs of one or two thousand feet\n" +
|
||||
"in height; for the gentle slope of the lava-streams,\n" +
|
||||
"due to their formerly liquid state, showed at a glance\n" +
|
||||
"how far the hard, rocky beds had once extended into\n" +
|
||||
"-----------------------------9051914041544843365972754266\n" +
|
||||
"Field1: value1\n" +
|
||||
"\r\n" +
|
||||
"But the amount of denudation which the strata have\n" +
|
||||
"in many places suffered, independently of the rate\n" +
|
||||
"of accumulation of the degraded matter, probably\n" +
|
||||
"offers the best evidence of the lapse of time. I remember\n" +
|
||||
"having been much struck with the evidence of\n" +
|
||||
"denudation, when viewing volcanic islands, which\n" +
|
||||
"have been worn by the waves and pared all round\n" +
|
||||
"into perpendicular cliffs of one or two thousand feet\n" +
|
||||
"in height; for the gentle slope of the lava-streams,\n" +
|
||||
"due to their formerly liquid state, showed at a glance\n" +
|
||||
"how far the hard, rocky beds had once extended into\n" +
|
||||
"the open ocean.\n" +
|
||||
"\r\n" +
|
||||
"-----------------------------9051914041544843365972754266--" +
|
||||
"===== 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++){
|
||||
//partition 0 to i
|
||||
ByteBuffer dataSeg = data.slice();
|
||||
dataSeg.position(0);
|
||||
dataSeg.limit(i);
|
||||
assertThat("First "+i,parser.parse(dataSeg,false),is(false));
|
||||
|
||||
//partition i
|
||||
dataSeg = data.slice();
|
||||
dataSeg.position(i);
|
||||
dataSeg.limit(i+1);
|
||||
assertThat("Second "+i,parser.parse(dataSeg,false),is(false));
|
||||
|
||||
//partition i to length
|
||||
dataSeg = data.slice();
|
||||
dataSeg.position(i+1);
|
||||
dataSeg.limit(length);
|
||||
assertThat("Third "+i,parser.parse(dataSeg,true),is(true));
|
||||
|
||||
assertThat(handler.fields, Matchers.contains( "Content-Disposition: form-data; name=\"text\"","<<COMPLETE>>"
|
||||
, "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
|
||||
, "Content-Type: text/plain","<<COMPLETE>>"
|
||||
, "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\""
|
||||
, "Content-Type: text/html","<<COMPLETE>>"
|
||||
, "Field1: value1", "Field2: value2", "Field3: value3"
|
||||
, "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>>"
|
||||
+ "<<LAST>>"
|
||||
+ "But the amount of denudation which the strata have\n" +
|
||||
"in many places suffered, independently of the rate\n" +
|
||||
"of accumulation of the degraded matter, probably\n" +
|
||||
"offers the best evidence of the lapse of time. I remember\n" +
|
||||
"having been much struck with the evidence of\n" +
|
||||
"denudation, when viewing volcanic islands, which\n" +
|
||||
"have been worn by the waves and pared all round\n" +
|
||||
"into perpendicular cliffs of one or two thousand feet\n" +
|
||||
"in height; for the gentle slope of the lava-streams,\n" +
|
||||
"due to their formerly liquid state, showed at a glance\n" +
|
||||
"how far the hard, rocky beds had once extended into\n" +
|
||||
"the open ocean.\n"+ "<<LAST>>")));
|
||||
|
||||
handler.clear();
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
int length = data.remaining();
|
||||
for (int i = 0; i < length - 1; i++)
|
||||
{
|
||||
//partition 0 to i
|
||||
ByteBuffer dataSeg = data.slice();
|
||||
dataSeg.position(0);
|
||||
dataSeg.limit(i);
|
||||
assertThat("First " + i, parser.parse(dataSeg, false), is(false));
|
||||
|
||||
//partition i
|
||||
dataSeg = data.slice();
|
||||
dataSeg.position(i);
|
||||
dataSeg.limit(i + 1);
|
||||
assertThat("Second " + i, parser.parse(dataSeg, false), is(false));
|
||||
|
||||
//partition i to length
|
||||
dataSeg = data.slice();
|
||||
dataSeg.position(i + 1);
|
||||
dataSeg.limit(length);
|
||||
assertThat("Third " + i, parser.parse(dataSeg, true), is(true));
|
||||
|
||||
assertThat(handler.fields, Matchers.contains("Content-Disposition: form-data; name=\"text\"", "<<COMPLETE>>"
|
||||
, "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
|
||||
, "Content-Type: text/plain", "<<COMPLETE>>"
|
||||
, "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\""
|
||||
, "Content-Type: text/html", "<<COMPLETE>>"
|
||||
, "Field1: value1", "Field2: value2", "Field3: value3"
|
||||
, "Field4: value4", "Field5: value5", "Field6: value6"
|
||||
, "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>"
|
||||
, "Field1: value1", "<<COMPLETE>>"));
|
||||
|
||||
|
||||
assertThat(handler.contentString(), is("text default" + "<<LAST>>"
|
||||
+ "Content of a.txt.\n" + "<<LAST>>"
|
||||
+ "<!DOCTYPE html><title>Content of a.html.</title>\n" + "<<LAST>>"
|
||||
+ "<<LAST>>"
|
||||
+ "But the amount of denudation which the strata have\n" +
|
||||
"in many places suffered, independently of the rate\n" +
|
||||
"of accumulation of the degraded matter, probably\n" +
|
||||
"offers the best evidence of the lapse of time. I remember\n" +
|
||||
"having been much struck with the evidence of\n" +
|
||||
"denudation, when viewing volcanic islands, which\n" +
|
||||
"have been worn by the waves and pared all round\n" +
|
||||
"into perpendicular cliffs of one or two thousand feet\n" +
|
||||
"in height; for the gentle slope of the lava-streams,\n" +
|
||||
"due to their formerly liquid state, showed at a glance\n" +
|
||||
"how far the hard, rocky beds had once extended into\n" +
|
||||
"the open ocean.\n" + "<<LAST>>"));
|
||||
|
||||
handler.clear();
|
||||
parser.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testGeneratedForm()
|
||||
public void testGeneratedForm()
|
||||
{
|
||||
TestHandler handler = new TestHandler()
|
||||
TestHandler handler = new TestHandler()
|
||||
{
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean content(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
super.content(buffer,last);
|
||||
super.content(buffer, last);
|
||||
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(""
|
||||
+ "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
|
||||
"\r\n" +
|
||||
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"part1\"\r\n" +
|
||||
"\n" +
|
||||
"wNfミxVamt\r\n" +
|
||||
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" +
|
||||
"Content-Disposition: form-data; name=\"part2\"\r\n" +
|
||||
"\r\n" +
|
||||
"&ᄈ취ᅢO\r\n" +
|
||||
+ "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
|
||||
"\r\n" +
|
||||
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"part1\"\r\n" +
|
||||
"\n" +
|
||||
"wNfミxVamt\r\n" +
|
||||
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" +
|
||||
"Content-Disposition: form-data; name=\"part2\"\r\n" +
|
||||
"\r\n" +
|
||||
"&ᄈ취ᅢO\r\n" +
|
||||
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--");
|
||||
|
||||
parser.parse(data,true);
|
||||
|
||||
parser.parse(data, true);
|
||||
assertThat(parser.getState(), is(State.END));
|
||||
assertThat(handler.fields.size(), is(2));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -670,27 +667,28 @@ public class MultiPartParserTest
|
|||
{
|
||||
List<String> fields = new ArrayList<>();
|
||||
List<String> content = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public void parsedField(String name, String value)
|
||||
{
|
||||
fields.add(name+": "+value);
|
||||
fields.add(name + ": " + value);
|
||||
}
|
||||
|
||||
|
||||
public String contentString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(String s : content) sb.append(s);
|
||||
for (String s : content)
|
||||
sb.append(s);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean headerComplete()
|
||||
{
|
||||
fields.add("<<COMPLETE>>");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean content(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
|
@ -701,11 +699,10 @@ public class MultiPartParserTest
|
|||
return last;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
fields.clear();
|
||||
content.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.http.jmh;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
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.MultiPartCaptureTest.MultipartExpectations;
|
||||
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.BenchmarkMode;
|
||||
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.State;
|
||||
import org.openjdk.jmh.annotations.TearDown;
|
||||
import org.openjdk.jmh.profile.CompilerProfiler;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.openjdk.jmh.runner.options.Options;
|
||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class MultiPartBenchmark
|
||||
public class MultiPartBenchmark
|
||||
{
|
||||
|
||||
public static final int MAX_FILE_SIZE = Integer.MAX_VALUE;
|
||||
|
@ -67,13 +60,14 @@ public class MultiPartBenchmark
|
|||
static File _file;
|
||||
static int _numSections;
|
||||
static int _numBytesPerSection;
|
||||
|
||||
|
||||
|
||||
public static List<String> data = new ArrayList<>();
|
||||
|
||||
static
|
||||
{
|
||||
// Capture of raw request body contents from various browsers
|
||||
|
||||
|
||||
// simple form - 2 fields
|
||||
data.add("browser-capture-form1-android-chrome");
|
||||
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-msie");
|
||||
data.add("browser-capture-form1-osx-safari");
|
||||
|
||||
|
||||
// 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");
|
||||
|
||||
|
||||
// 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-msie");
|
||||
|
||||
|
||||
// form submitted with simple file upload
|
||||
data.add("browser-capture-form-fileupload-android-chrome");
|
||||
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-msie");
|
||||
data.add("browser-capture-form-fileupload-safari");
|
||||
|
||||
|
||||
// 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-edge");
|
||||
|
@ -109,19 +103,19 @@ public class MultiPartBenchmark
|
|||
data.add("browser-capture-form-fileupload-alt-msie");
|
||||
data.add("browser-capture-form-fileupload-alt-safari");
|
||||
}
|
||||
|
||||
|
||||
@Param({"UTIL","HTTP"})
|
||||
|
||||
@Param({"UTIL", "HTTP"})
|
||||
public static String parserType;
|
||||
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public static void setupTrial() throws Exception
|
||||
{
|
||||
_file = File.createTempFile("test01",null);
|
||||
_file = File.createTempFile("test01", null);
|
||||
_file.deleteOnExit();
|
||||
|
||||
_numSections = 1;
|
||||
_numBytesPerSection = 1024*1024*10;
|
||||
_numBytesPerSection = 1024 * 1024 * 10;
|
||||
|
||||
_contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW";
|
||||
String initialBoundary = "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n";
|
||||
|
@ -130,17 +124,18 @@ public class MultiPartBenchmark
|
|||
String headerStart = "Content-Disposition: form-data; name=\"";
|
||||
|
||||
|
||||
for(int i=0; i<_numSections; i++) {
|
||||
for (int i = 0; i < _numSections; i++)
|
||||
{
|
||||
//boundary and headers
|
||||
if(i==0)
|
||||
if (i == 0)
|
||||
Files.write(_file.toPath(), initialBoundary.getBytes(), StandardOpenOption.APPEND);
|
||||
else
|
||||
Files.write(_file.toPath(), boundary.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(), new String("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND);
|
||||
|
||||
Files.write(_file.toPath(), ("part" + (i + 1)).getBytes(), StandardOpenOption.APPEND);
|
||||
Files.write(_file.toPath(), ("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND);
|
||||
|
||||
//append random data
|
||||
byte[] data = new byte[_numBytesPerSection];
|
||||
new Random().nextBytes(data);
|
||||
|
@ -149,25 +144,9 @@ public class MultiPartBenchmark
|
|||
|
||||
//closing boundary
|
||||
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
|
||||
@BenchmarkMode({Mode.AverageTime})
|
||||
@SuppressWarnings("deprecation")
|
||||
|
@ -175,44 +154,44 @@ public class MultiPartBenchmark
|
|||
{
|
||||
Path multipartRawFile = _file.toPath();
|
||||
Path outputDir = new File("/tmp").toPath();
|
||||
|
||||
|
||||
MultipartConfigElement config = newMultipartConfigElement(outputDir);
|
||||
|
||||
|
||||
try (InputStream in = Files.newInputStream(multipartRawFile))
|
||||
{
|
||||
switch(parserType)
|
||||
switch (parserType)
|
||||
{
|
||||
case "HTTP":
|
||||
{
|
||||
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, _contentType, config, outputDir.toFile());
|
||||
if(parser.getParts().size() != _numSections)
|
||||
if (parser.getParts().size() != _numSections)
|
||||
throw new IllegalStateException("Incorrect Parsing");
|
||||
for(Part p : parser.getParts()) {
|
||||
for (Part p : parser.getParts())
|
||||
{
|
||||
count += p.getSize();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "UTIL":
|
||||
{
|
||||
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, _contentType,config,outputDir.toFile());
|
||||
|
||||
// TODO this is using the http version of part (which should be the same anyway)
|
||||
if(parser.getParts().size() != _numSections)
|
||||
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, _contentType, config, outputDir.toFile());
|
||||
if (parser.getParts().size() != _numSections)
|
||||
throw new IllegalStateException("Incorrect Parsing");
|
||||
for(Part p : parser.getParts()) {
|
||||
for (Part p : parser.getParts())
|
||||
{
|
||||
count += p.getSize();
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Unknown parserType Parameter");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
|
@ -220,7 +199,7 @@ public class MultiPartBenchmark
|
|||
{
|
||||
_file = null;
|
||||
}
|
||||
|
||||
|
||||
private MultipartConfigElement newMultipartConfigElement(Path path)
|
||||
{
|
||||
return new MultipartConfigElement(path.toString(), MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD);
|
||||
|
@ -231,48 +210,48 @@ public class MultiPartBenchmark
|
|||
@SuppressWarnings("deprecation")
|
||||
public long testParser() throws Exception
|
||||
{
|
||||
for(String multiPart : data)
|
||||
for (String multiPart : data)
|
||||
{
|
||||
Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw");
|
||||
Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt");
|
||||
|
||||
Path outputDir = new File("/tmp").toPath();
|
||||
|
||||
|
||||
MultipartExpectations multipartExpectations = new MultipartExpectations(expectationPath);
|
||||
MultipartConfigElement config = newMultipartConfigElement(outputDir);
|
||||
|
||||
try (InputStream in = Files.newInputStream(multipartRawFile))
|
||||
{
|
||||
switch(parserType)
|
||||
switch (parserType)
|
||||
{
|
||||
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());
|
||||
for(Part p : parser.getParts()) {
|
||||
count += p.getSize();
|
||||
}
|
||||
count += p.getSize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
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());
|
||||
|
||||
// TODO this is using the http version of part (which should be the same anyway)
|
||||
for(Part p : parser.getParts()) {
|
||||
count += p.getSize();
|
||||
}
|
||||
count += p.getSize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown parserType Parameter");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws RunnerException
|
||||
|
||||
public static void main(String[] args) throws RunnerException
|
||||
{
|
||||
Options opt = new OptionsBuilder()
|
||||
.include(MultiPartBenchmark.class.getSimpleName())
|
||||
|
@ -280,14 +259,6 @@ public class MultiPartBenchmark
|
|||
.measurementIterations(10)
|
||||
.forks(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();
|
||||
|
||||
new Runner(opt).run();
|
||||
|
|
Loading…
Reference in New Issue