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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -44,7 +45,9 @@ import javax.servlet.MultipartConfigElement;
|
|||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.eclipse.jetty.http.HttpGenerator.State;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.ByteArrayOutputStream2;
|
||||
import org.eclipse.jetty.util.LazyList;
|
||||
import org.eclipse.jetty.util.MultiException;
|
||||
|
@ -208,7 +211,7 @@ public class MultiPartInputStreamParser
|
|||
*/
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
return _headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
|
||||
|
@ -514,17 +517,14 @@ public class MultiPartInputStreamParser
|
|||
|
||||
|
||||
//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<>();
|
||||
|
||||
//if its not a multipart request, don't parse it
|
||||
if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
//sort out the location to which to write the files
|
||||
|
||||
if (_config.getLocation() == null)
|
||||
_tmpDir = _contextTmpDir;
|
||||
else if ("".equals(_config.getLocation()))
|
||||
|
@ -550,94 +550,95 @@ public class MultiPartInputStreamParser
|
|||
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
|
||||
String line = null;
|
||||
try
|
||||
|
||||
// ============ MY CODE ============= //
|
||||
Handler handler = new Handler();
|
||||
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
|
||||
|
||||
|
||||
// Create a buffer to store data from stream //
|
||||
final int _bufferSize = 16*1024; // <---- need to rename and move somewhere else
|
||||
byte[] data = new byte[_bufferSize];
|
||||
int len=0;
|
||||
|
||||
|
||||
while(true)
|
||||
{
|
||||
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;
|
||||
line=line.trim();
|
||||
while (line != null && !line.equals(boundary) && !line.equals(lastBoundary))
|
||||
{
|
||||
if (!badFormatLogged)
|
||||
try
|
||||
{
|
||||
LOG.warn("Badly formatted multipart request");
|
||||
badFormatLogged = true;
|
||||
len = _in.read(data);
|
||||
}
|
||||
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)
|
||||
throw new IOException("Missing initial multi part boundary");
|
||||
|
||||
// Empty multipart.
|
||||
if (line.equals(lastBoundary))
|
||||
|
||||
//check for exceptions
|
||||
if(_err != null)
|
||||
{
|
||||
return;
|
||||
|
||||
// Read each part
|
||||
boolean lastPart=false;
|
||||
|
||||
outer:while(!lastPart)
|
||||
}
|
||||
|
||||
//check we read to the end of the message
|
||||
if(parser.getState() != MultiPartParser.State.END)
|
||||
{
|
||||
String contentDisposition=null;
|
||||
String contentType=null;
|
||||
String contentTransferEncoding=null;
|
||||
_err = new IllegalStateException("Parser Not in End State: "+parser.getState());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Handler implements MultiPartParser.Handler
|
||||
{
|
||||
/* keep running total of size of bytes read from input
|
||||
* and throw an exception if exceeds MultipartConfigElement._maxRequestSize */
|
||||
private long total = 0;
|
||||
|
||||
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(key.toLowerCase(Locale.ENGLISH), value);
|
||||
if (key.equalsIgnoreCase("content-disposition"))
|
||||
contentDisposition=value;
|
||||
else if (key.equalsIgnoreCase("content-type"))
|
||||
contentType = value;
|
||||
|
||||
MultiMap<String> headers = new MultiMap<>();
|
||||
while(true)
|
||||
{
|
||||
line=((ReadLineInputStream)_in).readLine();
|
||||
|
||||
//No more input
|
||||
if(line==null)
|
||||
break outer;
|
||||
|
||||
//end of headers:
|
||||
if("".equals(line))
|
||||
break;
|
||||
|
||||
total += line.length();
|
||||
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
|
||||
throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
|
||||
|
||||
//get content-disposition and content-type
|
||||
int c=line.indexOf(':',0);
|
||||
if(c>0)
|
||||
{
|
||||
String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
|
||||
String value=line.substring(c+1,line.length()).trim();
|
||||
headers.put(key, value);
|
||||
if (key.equalsIgnoreCase("content-disposition"))
|
||||
contentDisposition=value;
|
||||
if (key.equalsIgnoreCase("content-type"))
|
||||
contentType = value;
|
||||
if(key.equals("content-transfer-encoding"))
|
||||
contentTransferEncoding=value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean headerComplete() {
|
||||
|
||||
try {
|
||||
|
||||
// Extract content-disposition
|
||||
boolean form_data=false;
|
||||
if(contentDisposition==null)
|
||||
{
|
||||
throw new IOException("Missing content-disposition");
|
||||
}
|
||||
|
||||
|
||||
QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
|
||||
String name=null;
|
||||
String filename=null;
|
||||
|
@ -652,11 +653,11 @@ public class MultiPartInputStreamParser
|
|||
else if(tl.startsWith("filename="))
|
||||
filename=filenameValue(t);
|
||||
}
|
||||
|
||||
|
||||
// Check disposition
|
||||
if(!form_data)
|
||||
{
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
//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.
|
||||
|
@ -665,163 +666,68 @@ public class MultiPartInputStreamParser
|
|||
//have not yet seen a name field.
|
||||
if(name==null)
|
||||
{
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//Have a new Part
|
||||
MultiPart part = new MultiPart(name, filename);
|
||||
part.setHeaders(headers);
|
||||
part.setContentType(contentType);
|
||||
_parts.add(name, part);
|
||||
part.open();
|
||||
|
||||
InputStream partInput = null;
|
||||
if ("base64".equalsIgnoreCase(contentTransferEncoding))
|
||||
{
|
||||
partInput = new Base64InputStream((ReadLineInputStream)_in);
|
||||
}
|
||||
else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
|
||||
{
|
||||
partInput = new FilterInputStream(_in)
|
||||
{
|
||||
@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;
|
||||
|
||||
|
||||
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("-------------------------");
|
||||
|
||||
_part = new MultiPart(name, filename);
|
||||
_part.setHeaders(headers);
|
||||
_part.setContentType(contentType);
|
||||
_parts.add(name, _part);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_err = e;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean content(ByteBuffer buffer, boolean last)
|
||||
{
|
||||
if (BufferUtil.hasContent(buffer))
|
||||
{
|
||||
try
|
||||
{
|
||||
int state=-2;
|
||||
int c;
|
||||
boolean cr=false;
|
||||
boolean lf=false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
//write the content data to the part
|
||||
_part.open();
|
||||
_part.write(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
|
||||
_part.close();
|
||||
}
|
||||
finally
|
||||
catch (IOException e)
|
||||
{
|
||||
part.close();
|
||||
_err = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (lastPart)
|
||||
{
|
||||
while(line!=null)
|
||||
line=((ReadLineInputStream)_in).readLine();
|
||||
}
|
||||
else
|
||||
throw new IOException("Incomplete parts");
|
||||
|
||||
if (last)
|
||||
reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_err = e;
|
||||
_part = null;
|
||||
contentDisposition = null;
|
||||
contentType = null;
|
||||
headers = new MultiMap<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setDeleteOnExit(boolean deleteOnExit)
|
||||
{
|
||||
_deleteOnExit = deleteOnExit;
|
||||
|
|
|
@ -354,20 +354,20 @@ public class MultiPartParser
|
|||
}
|
||||
}
|
||||
|
||||
if(last)
|
||||
if(last && BufferUtil.isEmpty(buffer))
|
||||
{
|
||||
if(_state == State.EPILOGUE)
|
||||
{
|
||||
_state = State.END;
|
||||
return _handler.messageComplete();
|
||||
}
|
||||
else if(BufferUtil.isEmpty(buffer))
|
||||
else
|
||||
{
|
||||
_handler.earlyEOF();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
@ -649,7 +649,7 @@ public class MultiPartParser
|
|||
}
|
||||
else
|
||||
{
|
||||
//print up to _partialBoundary of the search pattern
|
||||
//output up to _partialBoundary of the search pattern
|
||||
ByteBuffer content = _patternBuffer.slice();
|
||||
if (_state==State.FIRST_OCTETS)
|
||||
{
|
||||
|
|
|
@ -371,10 +371,6 @@ public class MultiPartParserTest
|
|||
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
|
||||
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);
|
||||
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.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 */
|
||||
parser.parse(data,true);
|
||||
assertThat(parser.getState(), is(State.END));
|
||||
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
|
||||
public void splitTest()
|
||||
{
|
||||
|
@ -504,7 +534,7 @@ public class MultiPartParserTest
|
|||
|
||||
|
||||
int length = data.remaining();
|
||||
for(int i = 0; i<length; i++){
|
||||
for(int i = 0; i<length-1; i++){
|
||||
//partition 0 to i
|
||||
ByteBuffer dataSeg = data.slice();
|
||||
dataSeg.position(0);
|
||||
|
@ -534,8 +564,6 @@ public class MultiPartParserTest
|
|||
, "Field1: value1","<<COMPLETE>>"));
|
||||
|
||||
|
||||
System.out.println("iteration "+i);
|
||||
System.out.println(handler.content);
|
||||
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>>"
|
||||
|
|
Loading…
Reference in New Issue