Merge remote-tracking branch 'eclipse/jetty-9.4.x-1027-Multipart' into jetty-9.4.x-1027-Multipart
This commit is contained in:
commit
0d4e202343
|
@ -18,11 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN;
|
|
||||||
import static org.eclipse.jetty.http.HttpTokens.LINE_FEED;
|
|
||||||
import static org.eclipse.jetty.http.HttpTokens.SPACE;
|
|
||||||
import static org.eclipse.jetty.http.HttpTokens.TAB;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
@ -118,6 +113,14 @@ public class MultiPartParser
|
||||||
{
|
{
|
||||||
public static final Logger LOG = Log.getLogger(MultiPartParser.class);
|
public static final Logger LOG = Log.getLogger(MultiPartParser.class);
|
||||||
|
|
||||||
|
static final byte COLON= (byte)':';
|
||||||
|
static final byte TAB= 0x09;
|
||||||
|
static final byte LINE_FEED= 0x0A;
|
||||||
|
static final byte CARRIAGE_RETURN= 0x0D;
|
||||||
|
static final byte SPACE= 0x20;
|
||||||
|
static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
|
||||||
|
static final byte SEMI_COLON= (byte)';';
|
||||||
|
|
||||||
|
|
||||||
// States
|
// States
|
||||||
public enum FieldState
|
public enum FieldState
|
||||||
|
@ -146,8 +149,7 @@ public class MultiPartParser
|
||||||
|
|
||||||
private final boolean DEBUG=LOG.isDebugEnabled();
|
private final boolean DEBUG=LOG.isDebugEnabled();
|
||||||
private final Handler _handler;
|
private final Handler _handler;
|
||||||
private final String _boundary;
|
private final SearchPattern _delimiterSearch;
|
||||||
private final SearchPattern _search;
|
|
||||||
private String _fieldName;
|
private String _fieldName;
|
||||||
private String _fieldValue;
|
private String _fieldValue;
|
||||||
|
|
||||||
|
@ -155,7 +157,6 @@ public class MultiPartParser
|
||||||
private FieldState _fieldState = FieldState.FIELD;
|
private FieldState _fieldState = FieldState.FIELD;
|
||||||
private int _partialBoundary = 2; // No CRLF if no preamble
|
private int _partialBoundary = 2; // No CRLF if no preamble
|
||||||
private boolean _cr;
|
private boolean _cr;
|
||||||
private boolean _quote;
|
|
||||||
|
|
||||||
private final StringBuilder _string=new StringBuilder();
|
private final StringBuilder _string=new StringBuilder();
|
||||||
private int _length;
|
private int _length;
|
||||||
|
@ -165,8 +166,7 @@ public class MultiPartParser
|
||||||
public MultiPartParser(Handler handler, String boundary)
|
public MultiPartParser(Handler handler, String boundary)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
_boundary = boundary;
|
_delimiterSearch = SearchPattern.compile("\r\n--"+boundary);
|
||||||
_search = SearchPattern.compile("\r\n--"+boundary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
|
@ -318,7 +318,7 @@ public class MultiPartParser
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case BODY_PART:
|
case BODY_PART:
|
||||||
handle = parseFields(buffer);
|
handle = parseMimePartHeaders(buffer);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PART:
|
case PART:
|
||||||
|
@ -351,10 +351,10 @@ public class MultiPartParser
|
||||||
{
|
{
|
||||||
if (_partialBoundary>0)
|
if (_partialBoundary>0)
|
||||||
{
|
{
|
||||||
int partial = _search.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary);
|
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary);
|
||||||
if (partial>0)
|
if (partial>0)
|
||||||
{
|
{
|
||||||
if (partial==_search.getLength())
|
if (partial==_delimiterSearch.getLength())
|
||||||
{
|
{
|
||||||
buffer.position(buffer.position()+partial-_partialBoundary);
|
buffer.position(buffer.position()+partial-_partialBoundary);
|
||||||
_partialBoundary = 0;
|
_partialBoundary = 0;
|
||||||
|
@ -370,15 +370,15 @@ public class MultiPartParser
|
||||||
_partialBoundary = 0;
|
_partialBoundary = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int delimiter = _search.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
|
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
|
||||||
if (delimiter>=0)
|
if (delimiter>=0)
|
||||||
{
|
{
|
||||||
buffer.position(delimiter-buffer.arrayOffset()+_search.getLength());
|
buffer.position(delimiter-buffer.arrayOffset()+_delimiterSearch.getLength());
|
||||||
setState(State.DELIMITER);
|
setState(State.DELIMITER);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_partialBoundary = _search.endsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
|
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining());
|
||||||
BufferUtil.clear(buffer);
|
BufferUtil.clear(buffer);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -428,7 +428,7 @@ public class MultiPartParser
|
||||||
/*
|
/*
|
||||||
* Parse the message headers and return true if the handler has signaled for a return
|
* Parse the message headers and return true if the handler has signaled for a return
|
||||||
*/
|
*/
|
||||||
protected boolean parseFields(ByteBuffer buffer)
|
protected boolean parseMimePartHeaders(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
// Process headers
|
// Process headers
|
||||||
while (_state==State.BODY_PART && buffer.hasRemaining())
|
while (_state==State.BODY_PART && buffer.hasRemaining())
|
||||||
|
@ -443,8 +443,8 @@ public class MultiPartParser
|
||||||
case FIELD:
|
case FIELD:
|
||||||
switch(b)
|
switch(b)
|
||||||
{
|
{
|
||||||
case HttpTokens.SPACE:
|
case SPACE:
|
||||||
case HttpTokens.TAB:
|
case TAB:
|
||||||
{
|
{
|
||||||
// Folded field value!
|
// Folded field value!
|
||||||
|
|
||||||
|
@ -467,7 +467,7 @@ public class MultiPartParser
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case HttpTokens.LINE_FEED:
|
case LINE_FEED:
|
||||||
{
|
{
|
||||||
handleField();
|
handleField();
|
||||||
setState(State.PART);
|
setState(State.PART);
|
||||||
|
@ -478,10 +478,6 @@ public class MultiPartParser
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// now handle the ch
|
|
||||||
if (b<HttpTokens.SPACE)
|
|
||||||
throw new BadMessageException();
|
|
||||||
|
|
||||||
// process previous header
|
// process previous header
|
||||||
handleField();
|
handleField();
|
||||||
|
|
||||||
|
@ -495,101 +491,96 @@ public class MultiPartParser
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IN_NAME:
|
case IN_NAME:
|
||||||
if (b==HttpTokens.COLON)
|
switch(b)
|
||||||
{
|
{
|
||||||
_fieldName=takeString();
|
case COLON:
|
||||||
_length=-1;
|
_fieldName=takeString();
|
||||||
setState(FieldState.VALUE);
|
_length=-1;
|
||||||
break;
|
setState(FieldState.VALUE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPACE:
|
||||||
|
//Ignore trailing whitespaces
|
||||||
|
setState(FieldState.AFTER_NAME);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_string.append((char)b);
|
||||||
|
_length=_string.length();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
if (b>HttpTokens.SPACE)
|
|
||||||
{
|
|
||||||
_string.append((char)b);
|
|
||||||
_length=_string.length();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//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)
|
switch(b)
|
||||||
{
|
{
|
||||||
_fieldName=takeString();
|
case COLON:
|
||||||
_length=-1;
|
_fieldName=takeString();
|
||||||
setState(FieldState.VALUE);
|
_length=-1;
|
||||||
break;
|
setState(FieldState.VALUE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LINE_FEED:
|
||||||
|
_fieldName=takeString();
|
||||||
|
_string.setLength(0);
|
||||||
|
_fieldValue="";
|
||||||
|
_length=-1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPACE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalCharacterException(_state,b,buffer);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
if (b==HttpTokens.LINE_FEED)
|
|
||||||
{
|
|
||||||
_fieldName=takeString();
|
|
||||||
_string.setLength(0);
|
|
||||||
_fieldValue="";
|
|
||||||
_length=-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b==HttpTokens.SPACE)
|
|
||||||
break;
|
|
||||||
|
|
||||||
throw new IllegalCharacterException(_state,b,buffer);
|
|
||||||
|
|
||||||
case VALUE:
|
case VALUE:
|
||||||
if (b>HttpTokens.SPACE || b<0)
|
switch(b)
|
||||||
{
|
{
|
||||||
_string.append((char)(0xff&b));
|
case LINE_FEED:
|
||||||
_length=_string.length();
|
_string.setLength(0);
|
||||||
setState(FieldState.IN_VALUE);
|
_fieldValue="";
|
||||||
break;
|
_length=-1;
|
||||||
|
|
||||||
|
setState(FieldState.FIELD);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPACE:
|
||||||
|
case TAB:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_string.append((char)(0xff&b));
|
||||||
|
_length=_string.length();
|
||||||
|
setState(FieldState.IN_VALUE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
if (b==HttpTokens.SPACE || b==HttpTokens.TAB)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (b==HttpTokens.LINE_FEED)
|
|
||||||
{
|
|
||||||
_string.setLength(0);
|
|
||||||
_fieldValue="";
|
|
||||||
_length=-1;
|
|
||||||
|
|
||||||
setState(FieldState.FIELD);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new IllegalCharacterException(_state,b,buffer);
|
|
||||||
|
|
||||||
case IN_VALUE:
|
case IN_VALUE:
|
||||||
if (b>=HttpTokens.SPACE || b<0 || b==HttpTokens.TAB)
|
switch(b)
|
||||||
{
|
{
|
||||||
if (_fieldValue!=null)
|
case SPACE:
|
||||||
{
|
_string.append((char)(0xff&b));
|
||||||
setString(_fieldValue);
|
break;
|
||||||
_fieldValue=null;
|
|
||||||
}
|
|
||||||
_string.append((char)(0xff&b));
|
|
||||||
if (b>HttpTokens.SPACE || b<0)
|
|
||||||
_length=_string.length();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b==HttpTokens.LINE_FEED)
|
case LINE_FEED:
|
||||||
{
|
if (_length > 0)
|
||||||
if (_length > 0)
|
{
|
||||||
{
|
_fieldValue=takeString();
|
||||||
_fieldValue=takeString();
|
_length=-1;
|
||||||
_length=-1;
|
}
|
||||||
}
|
setState(FieldState.FIELD);
|
||||||
setState(FieldState.FIELD);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalCharacterException(_state,b,buffer);
|
default:
|
||||||
|
_string.append((char)(0xff&b));
|
||||||
|
if (b>SPACE || b<0)
|
||||||
|
_length=_string.length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(_state.toString());
|
throw new IllegalStateException(_state.toString());
|
||||||
|
@ -714,11 +705,11 @@ public class MultiPartParser
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private static class IllegalCharacterException extends BadMessageException
|
private static class IllegalCharacterException extends IllegalArgumentException
|
||||||
{
|
{
|
||||||
private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
|
private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
super(400,String.format("Illegal character 0x%X",ch));
|
super(String.format("Illegal character 0x%X",ch));
|
||||||
// Bug #460642 - don't reveal buffers to end user
|
// Bug #460642 - don't reveal buffers to end user
|
||||||
LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
|
LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,9 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class MultiPartParserTest
|
public class MultiPartParserTest
|
||||||
|
@ -158,6 +156,7 @@ public class MultiPartParserTest
|
||||||
@Override
|
@Override
|
||||||
public void parsedHeader(String name, String value)
|
public void parsedHeader(String name, String value)
|
||||||
{
|
{
|
||||||
|
System.err.println("Value='"+value+"'");
|
||||||
fields.add(name+": "+value);
|
fields.add(name+": "+value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,9 +180,8 @@ public class MultiPartParserTest
|
||||||
parser.parse(data,false);
|
parser.parse(data,false);
|
||||||
assertTrue(parser.isState(State.PART));
|
assertTrue(parser.isState(State.PART));
|
||||||
assertThat(data.remaining(),is(7));
|
assertThat(data.remaining(),is(7));
|
||||||
|
System.err.println(fields);
|
||||||
assertThat(fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<<COMPLETE>>"));
|
assertThat(fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<<COMPLETE>>"));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue