Rewrote the parse method to use the MultiPartParser, some error handling not implemented so only passing half of the tests.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2018-03-18 21:53:54 +11:00
parent fcd2d49da1
commit df83cca010
3 changed files with 176 additions and 242 deletions

View File

@ -29,6 +29,7 @@ import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -44,7 +45,9 @@ import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.http.Part; import javax.servlet.http.Part;
import org.eclipse.jetty.http.HttpGenerator.State;
import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ByteArrayOutputStream2; import org.eclipse.jetty.util.ByteArrayOutputStream2;
import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.MultiException;
@ -514,17 +517,14 @@ public class MultiPartInputStreamParser
//initialize //initialize
long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
_parts = new MultiMap<>(); _parts = new MultiMap<>();
//if its not a multipart request, don't parse it //if its not a multipart request, don't parse it
if (_contentType == null || !_contentType.startsWith("multipart/form-data")) if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
return; return;
try
{
//sort out the location to which to write the files
//sort out the location to which to write the files
if (_config.getLocation() == null) if (_config.getLocation() == null)
_tmpDir = _contextTmpDir; _tmpDir = _contextTmpDir;
else if ("".equals(_config.getLocation())) else if ("".equals(_config.getLocation()))
@ -550,86 +550,87 @@ public class MultiPartInputStreamParser
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim()); contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
} }
String boundary="--"+contentTypeBoundary;
String lastBoundary=boundary+"--";
byte[] byteBoundary=lastBoundary.getBytes(StandardCharsets.ISO_8859_1);
// Get first boundary // ============ MY CODE ============= //
String line = null; Handler handler = new Handler();
try MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
{
line=((ReadLineInputStream)_in).readLine();
}
catch (IOException e)
{
LOG.warn("Badly formatted multipart request");
throw e;
}
if (line == null)
throw new IOException("Missing content for multipart request");
boolean badFormatLogged = false; // Create a buffer to store data from stream //
line=line.trim(); final int _bufferSize = 16*1024; // <---- need to rename and move somewhere else
while (line != null && !line.equals(boundary) && !line.equals(lastBoundary)) byte[] data = new byte[_bufferSize];
int len=0;
while(true)
{ {
if (!badFormatLogged) try
{ {
LOG.warn("Badly formatted multipart request"); len = _in.read(data);
badFormatLogged = true; }
catch (IOException e)
{
_err = e;
return;
}
if(len > 0)
{
ByteBuffer buffer = BufferUtil.toBuffer(data);
buffer.limit(len);
parser.parse(buffer, false);
}
else if (len == -1)
{
parser.parse(BufferUtil.EMPTY_BUFFER, true);
break;
} }
line=((ReadLineInputStream)_in).readLine();
line=(line==null?line:line.trim());
} }
if (line == null || line.length() == 0) //check for exceptions
throw new IOException("Missing initial multi part boundary"); if(_err != null)
{
// Empty multipart.
if (line.equals(lastBoundary))
return; return;
}
// Read each part //check we read to the end of the message
boolean lastPart=false; if(parser.getState() != MultiPartParser.State.END)
outer:while(!lastPart)
{ {
String contentDisposition=null; _err = new IllegalStateException("Parser Not in End State: "+parser.getState());
String contentType=null; }
String contentTransferEncoding=null;
MultiMap<String> headers = new MultiMap<>(); }
while(true)
{
line=((ReadLineInputStream)_in).readLine();
//No more input class Handler implements MultiPartParser.Handler
if(line==null) {
break outer; /* keep running total of size of bytes read from input
* and throw an exception if exceeds MultipartConfigElement._maxRequestSize */
private long total = 0;
//end of headers: private MultiPart _part=null;
if("".equals(line)) private String contentDisposition=null;
break; private String contentType=null;
private MultiMap<String> headers = new MultiMap<>();
total += line.length(); @Override
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) public boolean messageComplete() { return true; }
throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
//get content-disposition and content-type @Override
int c=line.indexOf(':',0); public void parsedField(String key, String value)
if(c>0) {
{ // Add to headers and mark if one of these fields. //
String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH); headers.put(key.toLowerCase(Locale.ENGLISH), value);
String value=line.substring(c+1,line.length()).trim(); if (key.equalsIgnoreCase("content-disposition"))
headers.put(key, value); contentDisposition=value;
if (key.equalsIgnoreCase("content-disposition")) else if (key.equalsIgnoreCase("content-type"))
contentDisposition=value; contentType = value;
if (key.equalsIgnoreCase("content-type"))
contentType = value; }
if(key.equals("content-transfer-encoding"))
contentTransferEncoding=value; @Override
} public boolean headerComplete() {
}
try {
// Extract content-disposition // Extract content-disposition
boolean form_data=false; boolean form_data=false;
@ -656,7 +657,7 @@ public class MultiPartInputStreamParser
// Check disposition // Check disposition
if(!form_data) if(!form_data)
{ {
continue; return false;
} }
//It is valid for reset and submit buttons to have an empty name. //It is valid for reset and submit buttons to have an empty name.
//If no name is supplied, the browser skips sending the info for that field. //If no name is supplied, the browser skips sending the info for that field.
@ -665,163 +666,68 @@ public class MultiPartInputStreamParser
//have not yet seen a name field. //have not yet seen a name field.
if(name==null) if(name==null)
{ {
continue; return false;
} }
//Have a new Part //Have a new Part
MultiPart part = new MultiPart(name, filename); System.out.println("-------------------------");
part.setHeaders(headers); System.out.println("Creating New Part");
part.setContentType(contentType); System.out.println("Name: "+name);
_parts.add(name, part); System.out.println("Filename: "+filename);
part.open(); System.out.println("Headers:\n"+headers);
System.out.println("ContentType:\n"+contentType);
System.out.println("-------------------------");
InputStream partInput = null; _part = new MultiPart(name, filename);
if ("base64".equalsIgnoreCase(contentTransferEncoding)) _part.setHeaders(headers);
{ _part.setContentType(contentType);
partInput = new Base64InputStream((ReadLineInputStream)_in); _parts.add(name, _part);
} }
else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) catch (Exception e)
{ {
partInput = new FilterInputStream(_in) _err = e;
{ return true;
@Override }
public int read() throws IOException
{
int c = in.read();
if (c >= 0 && c == '=')
{
int hi = in.read();
int lo = in.read();
if (hi < 0 || lo < 0)
{
throw new IOException("Unexpected end to quoted-printable byte");
}
char[] chars = new char[] { (char)hi, (char)lo };
c = Integer.parseInt(new String(chars),16);
}
return c;
}
};
}
else
partInput = _in;
return false;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
if (BufferUtil.hasContent(buffer))
{
try try
{ {
int state=-2; //write the content data to the part
int c; _part.open();
boolean cr=false; _part.write(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
boolean lf=false; _part.close();
// loop for all lines
while(true)
{
int b=0;
while((c=(state!=-2)?state:partInput.read())!=-1)
{
total ++;
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
state=-2;
// look for CR and/or LF
if(c==13||c==10)
{
if(c==13)
{
partInput.mark(1);
int tmp=partInput.read();
if (tmp!=10)
partInput.reset();
else
state=tmp;
}
break;
}
// Look for boundary
if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
{
b++;
}
else
{
// Got a character not part of the boundary, so we don't have the boundary marker.
// Write out as many chars as we matched, then the char we're looking at.
if(cr)
part.write(13);
if(lf)
part.write(10);
cr=lf=false;
if(b>0)
part.write(byteBoundary,0,b);
b=-1;
part.write(c);
}
}
// Check for incomplete boundary match, writing out the chars we matched along the way
if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
{
if(cr)
part.write(13);
if(lf)
part.write(10);
cr=lf=false;
part.write(byteBoundary,0,b);
b=-1;
}
// Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
if(b>0||c==-1)
{
if(b==byteBoundary.length)
lastPart=true;
if(state==10)
state=-2;
break;
}
// handle CR LF
if(cr)
part.write(13);
if(lf)
part.write(10);
cr=(c==13);
lf=(c==10||state==10);
if(state==10)
state=-2;
}
} }
finally catch (IOException e)
{ {
part.close(); _err = e;
return true;
} }
} }
if (lastPart)
{ if (last)
while(line!=null) reset();
line=((ReadLineInputStream)_in).readLine();
} return false;
else
throw new IOException("Incomplete parts");
} }
catch (Exception e)
public void reset()
{ {
_err = e; _part = null;
contentDisposition = null;
contentType = null;
headers = new MultiMap<>();
} }
} }
public void setDeleteOnExit(boolean deleteOnExit) public void setDeleteOnExit(boolean deleteOnExit)
{ {
_deleteOnExit = deleteOnExit; _deleteOnExit = deleteOnExit;

View File

@ -354,20 +354,20 @@ public class MultiPartParser
} }
} }
if(last) if(last && BufferUtil.isEmpty(buffer))
{ {
if(_state == State.EPILOGUE) if(_state == State.EPILOGUE)
{ {
_state = State.END; _state = State.END;
return _handler.messageComplete(); return _handler.messageComplete();
} }
else if(BufferUtil.isEmpty(buffer)) else
{ {
_handler.earlyEOF(); _handler.earlyEOF();
return true; return true;
} }
} }
return handle; return handle;
} }
@ -649,7 +649,7 @@ public class MultiPartParser
} }
else else
{ {
//print up to _partialBoundary of the search pattern //output up to _partialBoundary of the search pattern
ByteBuffer content = _patternBuffer.slice(); ByteBuffer content = _patternBuffer.slice();
if (_state==State.FIRST_OCTETS) if (_state==State.FIRST_OCTETS)
{ {

View File

@ -371,10 +371,6 @@ public class MultiPartParserTest
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>")); assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>")); assertThat(handler.content,Matchers.contains("Hello","<<LAST>>"));
parser.parse(data,false);
assertThat(parser.getState(), is(State.EPILOGUE));
assertThat(data.remaining(),is(0));
parser.parse(data,true); parser.parse(data,true);
assertThat(parser.getState(), is(State.END)); assertThat(parser.getState(), is(State.END));
} }
@ -412,17 +408,51 @@ public class MultiPartParserTest
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>","powerLevel: 9001","<<COMPLETE>>")); assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>","powerLevel: 9001","<<COMPLETE>>"));
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>","secondary\r\ncontent","<<LAST>>")); assertThat(handler.content, Matchers.contains("Hello","<<LAST>>","secondary\r\ncontent","<<LAST>>"));
/* Test Progression to EPILOGUE State */
parser.parse(data,false);
assertThat(parser.getState(), is(State.EPILOGUE));
assertThat(data.remaining(),is(0));
/* Test Progression to END State */ /* Test Progression to END State */
parser.parse(data,true); parser.parse(data,true);
assertThat(parser.getState(), is(State.END)); assertThat(parser.getState(), is(State.END));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(),is(0));
} }
@Test
public void testEndState() {
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\n"+
"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");
/* Test Progression to END State */
parser.parse(data,true);
assertThat(parser.getState(), is(State.END));
assertThat(data.remaining(),is(0));
}
@Test @Test
public void splitTest() public void splitTest()
{ {
@ -504,7 +534,7 @@ public class MultiPartParserTest
int length = data.remaining(); int length = data.remaining();
for(int i = 0; i<length; i++){ for(int i = 0; i<length-1; i++){
//partition 0 to i //partition 0 to i
ByteBuffer dataSeg = data.slice(); ByteBuffer dataSeg = data.slice();
dataSeg.position(0); dataSeg.position(0);
@ -534,8 +564,6 @@ public class MultiPartParserTest
, "Field1: value1","<<COMPLETE>>")); , "Field1: value1","<<COMPLETE>>"));
System.out.println("iteration "+i);
System.out.println(handler.content);
assertThat(handler.contentString(), is(new String("text default"+"<<LAST>>" assertThat(handler.contentString(), is(new String("text default"+"<<LAST>>"
+ "Content of a.txt.\n"+"<<LAST>>" + "Content of a.txt.\n"+"<<LAST>>"
+ "<!DOCTYPE html><title>Content of a.html.</title>\n"+"<<LAST>>" + "<!DOCTYPE html><title>Content of a.html.</title>\n"+"<<LAST>>"