Reworked MultipartParser with additional option to accept CR as CRLF.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2018-03-20 11:40:55 +11:00
parent 0ce3ccd4eb
commit 93b8161afb
4 changed files with 86 additions and 39 deletions

View File

@ -497,6 +497,7 @@ public class MultiPartInputStreamParser
{
if (_err != null)
{
_err.addSuppressed(new Throwable());
if (_err instanceof IOException)
throw (IOException)_err;
if (_err instanceof IllegalStateException)
@ -551,7 +552,6 @@ public class MultiPartInputStreamParser
}
// ============ MY CODE ============= //
Handler handler = new Handler();
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
@ -596,7 +596,12 @@ public class MultiPartInputStreamParser
//check we read to the end of the message
if(parser.getState() != MultiPartParser.State.END)
{
_err = new IllegalStateException("Parser Not in End State: "+parser.getState());
_err = new IOException("Incomplete Multipart");
}
if(LOG.isDebugEnabled())
{
LOG.debug("Parsing Complete {} err={}",parser,_err);
}
}
@ -669,15 +674,7 @@ public class MultiPartInputStreamParser
return false;
}
//Have a new Part
System.out.println("-------------------------");
System.out.println("Creating New Part");
System.out.println("Name: "+name);
System.out.println("Filename: "+filename);
System.out.println("Headers:\n"+headers);
System.out.println("ContentType:\n"+contentType);
System.out.println("-------------------------");
//create the new part
_part = new MultiPart(name, filename);
_part.setHeaders(headers);
_part.setContentType(contentType);

View File

@ -152,6 +152,8 @@ public class MultiPartParser
private final boolean DEBUG=LOG.isDebugEnabled();
private final Handler _handler;
private final SearchPattern _delimiterSearch;
private final boolean _acceptCrAsLineTermination;
private String _fieldName;
private String _fieldValue;
@ -159,6 +161,7 @@ public class MultiPartParser
private FieldState _fieldState = FieldState.FIELD;
private int _partialBoundary = 2; // No CRLF if no preamble
private boolean _cr;
private byte _next;
private ByteBuffer _patternBuffer;
private final StringBuilder _string=new StringBuilder();
@ -167,12 +170,25 @@ public class MultiPartParser
/* ------------------------------------------------------------------------------- */
public MultiPartParser(Handler handler, String boundary)
{
this(handler,boundary,false);
}
/* ------------------------------------------------------------------------------- */
/** TODO complete
*
* @param handler
* @param boundary
* @param acceptCR
*/
public MultiPartParser(Handler handler, String boundary, boolean acceptCR)
{
_handler = handler;
String delimiter = "\r\n--"+boundary;
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
_delimiterSearch = SearchPattern.compile(_patternBuffer.array());
_acceptCrAsLineTermination = acceptCR;
}
public void reset()
@ -254,9 +270,23 @@ public class MultiPartParser
}
/* ------------------------------------------------------------------------------- */
private byte next(ByteBuffer buffer)
private boolean hasNextByte(ByteBuffer buffer)
{
byte ch = buffer.get();
return BufferUtil.hasContent(buffer) || _next!=0;
}
/* ------------------------------------------------------------------------------- */
private byte getNextByte(ByteBuffer buffer, boolean last)
{
byte ch;
if (_next==0)
ch = buffer.get();
else
{
ch = _next;
_next = 0;
}
CharState s = __charState[0xff & ch];
switch(s)
@ -267,11 +297,18 @@ public class MultiPartParser
case CR:
if (_cr)
{
if (!_acceptCrAsLineTermination)
throw new BadMessageException("Bad EOL");
// TODO log compliance violation
return (byte)'\n';
}
_cr=true;
if (buffer.hasRemaining())
return next(buffer);
return getNextByte(buffer, last);
else if (_acceptCrAsLineTermination && last)
return '\n';
// Can return 0 here to indicate the need for more characters,
// because a real 0 in the buffer would cause a BadMessage below
@ -279,7 +316,14 @@ public class MultiPartParser
case LEGAL:
if (_cr)
{
if (!_acceptCrAsLineTermination)
throw new BadMessageException("Bad EOL");
// TODO log compliance violation
_next = ch;
_cr = false;
return (byte)'\n';
}
return ch;
case ILLEGAL:
@ -328,11 +372,11 @@ public class MultiPartParser
case DELIMITER:
case DELIMITER_PADDING:
case DELIMITER_CLOSE:
parseDelimiter(buffer);
parseDelimiter(buffer, last);
continue;
case BODY_PART:
handle = parseMimePartHeaders(buffer);
handle = parseMimePartHeaders(buffer, last);
break;
case FIRST_OCTETS:
@ -410,11 +454,11 @@ public class MultiPartParser
}
/* ------------------------------------------------------------------------------- */
private void parseDelimiter(ByteBuffer buffer)
private void parseDelimiter(ByteBuffer buffer, boolean last)
{
while (__delimiterStates.contains(_state) && buffer.hasRemaining())
while (__delimiterStates.contains(_state) && hasNextByte(buffer))
{
byte b=next(buffer);
byte b=getNextByte(buffer, last);
if (b==0)
return;
@ -454,13 +498,13 @@ public class MultiPartParser
/*
* Parse the message headers and return true if the handler has signaled for a return
*/
protected boolean parseMimePartHeaders(ByteBuffer buffer)
protected boolean parseMimePartHeaders(ByteBuffer buffer, boolean last)
{
// Process headers
while (_state==State.BODY_PART && buffer.hasRemaining())
while (_state==State.BODY_PART && hasNextByte(buffer))
{
// process each character
byte b=next(buffer);
byte b=getNextByte(buffer, last);
if (b==0)
break;
@ -629,6 +673,14 @@ public class MultiPartParser
protected boolean parseOctetContent(ByteBuffer buffer)
{
//handle the next content that was held because of \r as \r\n
if (_next!=0)
{
_handler.content(BufferUtil.toBuffer(new byte[] {_next}),false);
_next = 0;
}
//Starts With
if (_partialBoundary>0)
{

View File

@ -77,7 +77,9 @@ public class MultiPartInputStreamTest
"Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
"Content-Type: application/octet-stream\r\n\r\n"+
"How now brown cow."+
"\r\n--" + boundary + "-\r\n\r\n";
"\r\n--" + boundary + "-\r\n"
+ "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"
+ "\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
@ -92,6 +94,7 @@ public class MultiPartInputStreamTest
}
catch (IOException e)
{
System.err.println(e.getMessage());
assertTrue(e.getMessage().startsWith("Incomplete"));
}
}

View File

@ -450,7 +450,7 @@ public class MultiPartParserTest
@Test
public void testEndState() {
public void testCrAsLineTermination() {
TestHandler handler = new TestHandler()
{
@Override public boolean messageComplete(){ return true; }
@ -462,19 +462,14 @@ public class MultiPartParserTest
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"AaB03x");
MultiPartParser parser = new MultiPartParser(handler,"AaB03x",true);
ByteBuffer data = BufferUtil.toBuffer(" "+
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r"+
"content-disposition: form-data; name=\"field1\"\r"+
"\r"+
"Joe Blow\r\n"+
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+
"Content-Type: text/plain\r\n"+
"\r\n"+"aaaa"+
"bbbbb"+"\r\n" +
"--AaB03x--\r\n");
"--AaB03x--\r");
/* Test Progression to END State */