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:
parent
fcd2d49da1
commit
df83cca010
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>>"
|
||||||
|
|
Loading…
Reference in New Issue