work in progress

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2018-03-09 09:38:23 +11:00
parent dc67cb5241
commit b0325f8299
2 changed files with 127 additions and 252 deletions

View File

@ -28,10 +28,8 @@ import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import org.eclipse.jetty.http.HttpParser.RequestHandler; import org.eclipse.jetty.http.HttpParser.RequestHandler;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.SearchPattern; import org.eclipse.jetty.util.SearchPattern;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -120,7 +118,6 @@ public class MultiPartParser
{ {
public static final Logger LOG = Log.getLogger(MultiPartParser.class); public static final Logger LOG = Log.getLogger(MultiPartParser.class);
public final static Trie<MimeField> CACHE = new ArrayTrie<>(2048);
// States // States
public enum FieldState public enum FieldState
@ -129,11 +126,7 @@ public class MultiPartParser
IN_NAME, IN_NAME,
AFTER_NAME, AFTER_NAME,
VALUE, VALUE,
IN_VALUE, IN_VALUE
PARAM,
PARAM_NAME,
PARAM_VALUE
} }
// States // States
@ -155,10 +148,8 @@ public class MultiPartParser
private final Handler _handler; private final Handler _handler;
private final String _boundary; private final String _boundary;
private final SearchPattern _search; private final SearchPattern _search;
private MimeField _field; private String _fieldName;
private String _headerString; private String _fieldValue;
private String _valueString;
private int _headerBytes;
private State _state = State.PREAMBLE; private State _state = State.PREAMBLE;
private FieldState _fieldState = FieldState.FIELD; private FieldState _fieldState = FieldState.FIELD;
@ -169,12 +160,6 @@ public class MultiPartParser
private final StringBuilder _string=new StringBuilder(); private final StringBuilder _string=new StringBuilder();
private int _length; private int _length;
static
{
CACHE.put(new MimeField("Content-Disposition","form-data"));
CACHE.put(new MimeField("Content-Type","text/plain"));
}
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public MultiPartParser(Handler handler, String boundary) public MultiPartParser(Handler handler, String boundary)
@ -330,24 +315,30 @@ public class MultiPartParser
case DELIMITER_PADDING: case DELIMITER_PADDING:
case DELIMITER_CLOSE: case DELIMITER_CLOSE:
parseDelimiter(buffer); parseDelimiter(buffer);
break; continue;
case BODY_PART: case BODY_PART:
handle = parseFields(buffer); handle = parseFields(buffer);
break; break;
case PART: case PART:
// TODO
handle = true;
break;
case EPILOGUE:
// TODO
handle = true;
break; break;
case END: case END:
break; // TODO
handle = true;
case EPILOGUE:
break; break;
default: default:
break; throw new IllegalStateException();
} }
} }
@ -442,37 +433,38 @@ public class MultiPartParser
*/ */
protected boolean parseFields(ByteBuffer buffer) protected boolean parseFields(ByteBuffer buffer)
{ {
/*
// Process headers // Process headers
while ((_state==State.HEADER && buffer.hasRemaining()) while (_state==State.BODY_PART && buffer.hasRemaining())
{ {
// process each character // process each character
byte b=next(buffer); byte b=next(buffer);
if (b==0) if (b==0)
break; break;
switch (_fieldState) switch (_fieldState)
{ {
case FIELD: case FIELD:
switch(b) switch(b)
{ {
case HttpTokens.COLON:
case HttpTokens.SPACE: case HttpTokens.SPACE:
case HttpTokens.TAB: case HttpTokens.TAB:
{ {
// header value without name - continuation? // Folded field value!
if (_valueString==null)
if (_fieldName==null)
throw new IllegalStateException("First field folded");
if (_fieldValue==null)
{ {
_string.setLength(0); _string.setLength(0);
_length=0; _length=0;
} }
else else
{ {
setString(_valueString); setString(_fieldValue);
_string.append(' '); _string.append(' ');
_length++; _length++;
_valueString=null; _fieldValue=null;
} }
setState(FieldState.VALUE); setState(FieldState.VALUE);
break; break;
@ -480,76 +472,11 @@ public class MultiPartParser
case HttpTokens.LINE_FEED: case HttpTokens.LINE_FEED:
{ {
// process previous header handleField();
if (_state==State.HEADER) setState(State.PART);
parsedHeader(); if (_handler.headerComplete())
else return true;
parsedTrailer(); break;
_contentPosition=0;
// End of headers or trailers?
if (_state==State.TRAILER)
{
setState(State.END);
return _handler.messageComplete();
}
// Was there a required host header?
if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"No Host");
}
// is it a response that cannot have a body?
if (_responseHandler !=null && // response
(_responseStatus == 304 || // not-modified response
_responseStatus == 204 || // no-content response
_responseStatus < 200)) // 1xx response
_endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
// else if we don't know framing
else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
{
if (_responseStatus == 0 // request
|| _responseStatus == 304 // not-modified response
|| _responseStatus == 204 // no-content response
|| _responseStatus < 200) // 1xx response
_endOfContent=EndOfContent.NO_CONTENT;
else
_endOfContent=EndOfContent.EOF_CONTENT;
}
// How is the message ended?
switch (_endOfContent)
{
case EOF_CONTENT:
{
setState(State.EOF_CONTENT);
boolean handle=_handler.headerComplete();
_headerComplete=true;
return handle;
}
case CHUNKED_CONTENT:
{
setState(State.CHUNKED_CONTENT);
boolean handle=_handler.headerComplete();
_headerComplete=true;
return handle;
}
case NO_CONTENT:
{
setState(State.END);
return handleHeaderContentMessage();
}
default:
{
setState(State.CONTENT);
boolean handle=_handler.headerComplete();
_headerComplete=true;
return handle;
}
}
} }
default: default:
@ -559,90 +486,7 @@ public class MultiPartParser
throw new BadMessageException(); throw new BadMessageException();
// process previous header // process previous header
if (_state==State.HEADER) handleField();
parsedHeader();
else
parsedTrailer();
// handle new header
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
HttpField cached_field=_fieldCache==null?null:_fieldCache.getBest(buffer,-1,buffer.remaining());
if (cached_field==null)
cached_field=CACHE.getBest(buffer,-1,buffer.remaining());
if (cached_field!=null)
{
String n = cached_field.getName();
String v = cached_field.getValue();
if (!_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
{
// Have to get the fields exactly from the buffer to match case
String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
if (!n.equals(en))
{
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,en);
n = en;
cached_field = new HttpField(cached_field.getHeader(),n,v);
}
}
if (v!=null && !_compliances.contains(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE))
{
String ev = BufferUtil.toString(buffer,buffer.position()+n.length()+1,v.length(),StandardCharsets.ISO_8859_1);
if (!v.equals(ev))
{
handleViolation(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE,ev+"!="+v);
v = ev;
cached_field = new HttpField(cached_field.getHeader(),n,v);
}
}
_header=cached_field.getHeader();
_headerString=n;
if (v==null)
{
// Header only
setState(FieldState.VALUE);
_string.setLength(0);
_length=0;
buffer.position(buffer.position()+n.length()+1);
break;
}
else
{
// Header and value
int pos=buffer.position()+n.length()+v.length()+1;
byte peek=buffer.get(pos);
if (peek==HttpTokens.CARRIAGE_RETURN || peek==HttpTokens.LINE_FEED)
{
_field=cached_field;
_valueString=v;
setState(FieldState.IN_VALUE);
if (peek==HttpTokens.CARRIAGE_RETURN)
{
_cr=true;
buffer.position(pos+1);
}
else
buffer.position(pos);
break;
}
else
{
setState(FieldState.IN_VALUE);
setString(v);
buffer.position(pos);
break;
}
}
}
}
// New header // New header
setState(FieldState.IN_NAME); setState(FieldState.IN_NAME);
@ -654,60 +498,49 @@ public class MultiPartParser
break; break;
case IN_NAME: case IN_NAME:
if (b>HttpTokens.SPACE && b!=HttpTokens.COLON) if (b==HttpTokens.COLON)
{ {
if (_header!=null) _fieldName=takeString();
{ _length=-1;
setString(_header.asString()); setState(FieldState.VALUE);
_header=null; break;
_headerString=null;
} }
if (b>HttpTokens.SPACE)
{
_string.append((char)b); _string.append((char)b);
_length=_string.length(); _length=_string.length();
break; break;
} }
// Fallthrough //Ignore trailing whitespaces
if (b==HttpTokens.SPACE)
{
setState(FieldState.AFTER_NAME);
break;
}
throw new IllegalCharacterException(_state,b,buffer);
case AFTER_NAME: case AFTER_NAME:
if (b==HttpTokens.COLON) if (b==HttpTokens.COLON)
{ {
if (_headerString==null) _fieldName=takeString();
{
_headerString=takeString();
_header=HttpHeader.CACHE.get(_headerString);
}
_length=-1; _length=-1;
setState(FieldState.VALUE); setState(FieldState.VALUE);
break; break;
} }
if (b==HttpTokens.LINE_FEED) if (b==HttpTokens.LINE_FEED)
{ {
if (_headerString==null) _fieldName=takeString();
{
_headerString=takeString();
_header=HttpHeader.CACHE.get(_headerString);
}
_string.setLength(0); _string.setLength(0);
_valueString=""; _fieldValue="";
_length=-1; _length=-1;
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
{
setState(FieldState.FIELD);
break;
}
} }
//Ignore trailing whitespaces if (b==HttpTokens.SPACE)
if (b==HttpTokens.SPACE && !complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,null))
{
setState(FieldState.AFTER_NAME);
break; break;
}
throw new IllegalCharacterException(_state,b,buffer); throw new IllegalCharacterException(_state,b,buffer);
@ -726,7 +559,7 @@ public class MultiPartParser
if (b==HttpTokens.LINE_FEED) if (b==HttpTokens.LINE_FEED)
{ {
_string.setLength(0); _string.setLength(0);
_valueString=""; _fieldValue="";
_length=-1; _length=-1;
setState(FieldState.FIELD); setState(FieldState.FIELD);
@ -737,11 +570,10 @@ public class MultiPartParser
case IN_VALUE: case IN_VALUE:
if (b>=HttpTokens.SPACE || b<0 || b==HttpTokens.TAB) if (b>=HttpTokens.SPACE || b<0 || b==HttpTokens.TAB)
{ {
if (_valueString!=null) if (_fieldValue!=null)
{ {
setString(_valueString); setString(_fieldValue);
_valueString=null; _fieldValue=null;
_field=null;
} }
_string.append((char)(0xff&b)); _string.append((char)(0xff&b));
if (b>HttpTokens.SPACE || b<0) if (b>HttpTokens.SPACE || b<0)
@ -753,7 +585,7 @@ public class MultiPartParser
{ {
if (_length > 0) if (_length > 0)
{ {
_valueString=takeString(); _fieldValue=takeString();
_length=-1; _length=-1;
} }
setState(FieldState.FIELD); setState(FieldState.FIELD);
@ -767,11 +599,16 @@ public class MultiPartParser
} }
} }
*/
return true; return true;
} }
/* ------------------------------------------------------------------------------- */
private void handleField()
{
if (_fieldName!=null && _fieldValue!=null)
_handler.parsedHeader(_fieldName,_fieldValue);
_fieldName = _fieldValue = null;
}
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
@ -793,7 +630,7 @@ public class MultiPartParser
private void setState(FieldState state) private void setState(FieldState state)
{ {
if (DEBUG) if (DEBUG)
LOG.debug("{}:{} --> {}",_state,_field,state); LOG.debug("{}:{} --> {}",_state,_fieldState,state);
_fieldState=state; _fieldState=state;
} }
@ -819,8 +656,7 @@ public class MultiPartParser
*/ */
public interface Handler public interface Handler
{ {
public default void parsedHeader(MimeField field) {} public default void parsedHeader(String name, String value) {}
public default void parsedParameter(MimeField field, String name, String value) {};
public default boolean headerComplete() {return false;} public default boolean headerComplete() {return false;}
public default boolean content(ByteBuffer item, boolean last) {return false;} public default boolean content(ByteBuffer item, boolean last) {return false;}
@ -843,24 +679,4 @@ public class MultiPartParser
} }
static class MimeField
{
final String _name;
final String _value;
final String _string;
public MimeField(String name, String value)
{
_name = name;
_value = value;
_string = name + ": " + value;
}
@Override
public String toString()
{
return _string;
}
}
} }

View File

@ -22,7 +22,10 @@ import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http.MultiPartParser.Handler;
import org.eclipse.jetty.http.MultiPartParser.State; import org.eclipse.jetty.http.MultiPartParser.State;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
@ -75,7 +78,12 @@ public class MultiPartParserTest
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("but not it isn't"); 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));
data = BufferUtil.toBuffer("DARX nor is this");
parser.parse(data,false); parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE)); assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0)); assertThat(data.remaining(),is(0));
@ -129,5 +137,56 @@ public class MultiPartParserTest
assertThat(data.remaining(),is(0)); assertThat(data.remaining(),is(0));
} }
@Test
public void testFirstPartNoFields()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n");
parser.parse(data,false);
assertTrue(parser.isState(State.PART));
assertThat(data.remaining(),is(0));
}
@Test
public void testFirstPartFields()
{
List<String> fields = new ArrayList<>();
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler()
{
@Override
public void parsedHeader(String name, String value)
{
new Throwable().printStackTrace();
fields.add(name+": "+value);
}
@Override
public boolean headerComplete()
{
fields.add("COMPLETE!");
return true;
}
},"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
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);
assertTrue(parser.isState(State.PART));
assertThat(data.remaining(),is(7));
assertThat(fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "COMPLETE!"));
}
} }