Implemented warnings for old MultiPartInputStreamParser when successfully parsing content not conforming to RFC.

Modified tests in new MultiPartInputStreamTest which were failing because they didn't comply with RFC.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2018-03-21 16:24:21 +11:00
parent 93b8161afb
commit 0f28107a9c
8 changed files with 267 additions and 220 deletions

View File

@ -21,16 +21,13 @@ package org.eclipse.jetty.http;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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;
import java.nio.file.StandardCopyOption;
@ -39,14 +36,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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;
@ -67,13 +61,14 @@ import org.eclipse.jetty.util.log.Logger;
public class MultiPartInputStreamParser
{
private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
private final int _bufferSize = 16*1024;
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
protected InputStream _in;
protected MultipartConfigElement _config;
protected String _contentType;
protected MultiMap<Part> _parts;
protected Exception _err;
protected Throwable _err;
protected File _tmpDir;
protected File _contextTmpDir;
protected boolean _deleteOnExit;
@ -185,8 +180,8 @@ public class MultiPartInputStreamParser
_out.flush();
_bout.writeTo(bos);
_out.close();
_bout = null;
}
_bout = null;
_out = bos;
}
@ -515,16 +510,17 @@ public class MultiPartInputStreamParser
//have we already parsed the input?
if (_parts != null || _err != null)
return;
try
{
//initialize
_parts = new MultiMap<>();
//if its not a multipart request, don't parse it
if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
return;
//initialize
_parts = new MultiMap<>();
//if its not a multipart request, don't parse it
if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
return;
//sort out the location to which to write the files
if (_config.getLocation() == null)
_tmpDir = _contextTmpDir;
@ -551,31 +547,33 @@ public class MultiPartInputStreamParser
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
}
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;
/* keep running total of size of bytes read from input
* and throw an exception if exceeds MultipartConfigElement._maxRequestSize */
long total = 0;
while(true)
{
try
{
len = _in.read(data);
}
catch (IOException e)
{
_err = e;
return;
}
len = _in.read(data);
if(len > 0)
{
total+=len;
if(_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
{
_err = new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
return;
}
ByteBuffer buffer = BufferUtil.toBuffer(data);
buffer.limit(len);
parser.parse(buffer, false);
@ -585,33 +583,43 @@ public class MultiPartInputStreamParser
parser.parse(BufferUtil.EMPTY_BUFFER, true);
break;
}
}
//check for exceptions
if(_err != null)
{
return;
}
//check we read to the end of the message
if(parser.getState() != MultiPartParser.State.END)
{
_err = new IOException("Incomplete Multipart");
if(parser.getState() == MultiPartParser.State.PREAMBLE)
_err = new IOException("Missing initial multi part boundary");
else
_err = new IOException("Incomplete Multipart");
}
if(LOG.isDebugEnabled())
{
LOG.debug("Parsing Complete {} err={}",parser,_err);
}
}
catch (Throwable e)
{
_err = e;
return;
}
}
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;
@ -783,49 +791,4 @@ public class MultiPartInputStreamParser
}
private static class Base64InputStream extends InputStream
{
ReadLineInputStream _in;
String _line;
byte[] _buffer;
int _pos;
public Base64InputStream(ReadLineInputStream rlis)
{
_in = rlis;
}
@Override
public int read() throws IOException
{
if (_buffer==null || _pos>= _buffer.length)
{
//Any CR and LF will be consumed by the readLine() call.
//We need to put them back into the bytes returned from this
//method because the parsing of the multipart content uses them
//as markers to determine when we've reached the end of a part.
_line = _in.readLine();
if (_line==null)
return -1; //nothing left
if (_line.startsWith("--"))
_buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
else if (_line.length()==0)
_buffer="\r\n".getBytes(); //blank line
else
{
ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
B64Code.decode(_line, baos);
baos.write(13);
baos.write(10);
_buffer = baos.toByteArray();
}
_pos=0;
}
return _buffer[_pos++];
}
}
}

View File

@ -152,7 +152,6 @@ public class MultiPartParser
private final boolean DEBUG=LOG.isDebugEnabled();
private final Handler _handler;
private final SearchPattern _delimiterSearch;
private final boolean _acceptCrAsLineTermination;
private String _fieldName;
private String _fieldValue;
@ -161,34 +160,22 @@ public class MultiPartParser
private FieldState _fieldState = FieldState.FIELD;
private int _partialBoundary = 2; // No CRLF if no preamble
private boolean _cr;
private byte _next;
private ByteBuffer _patternBuffer;
private final StringBuilder _string=new StringBuilder();
private int _length;
private int _totalHeaderLineLength = -1;
private int _maxHeaderLineLength = 998;
/* ------------------------------------------------------------------------------- */
public MultiPartParser(Handler handler, String boundary)
{
this(handler,boundary,false);
}
/* ------------------------------------------------------------------------------- */
/** TODO complete
*
* @param handler
* @param boundary
* @param acceptCR
*/
public MultiPartParser(Handler handler, String boundary, boolean acceptCR)
{
_handler = handler;
String delimiter = "\r\n--"+boundary;
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
_delimiterSearch = SearchPattern.compile(_patternBuffer.array());
_acceptCrAsLineTermination = acceptCR;
}
public void reset()
@ -272,21 +259,14 @@ public class MultiPartParser
/* ------------------------------------------------------------------------------- */
private boolean hasNextByte(ByteBuffer buffer)
{
return BufferUtil.hasContent(buffer) || _next!=0;
return BufferUtil.hasContent(buffer);
}
/* ------------------------------------------------------------------------------- */
private byte getNextByte(ByteBuffer buffer, boolean last)
private byte getNextByte(ByteBuffer buffer)
{
byte ch;
if (_next==0)
ch = buffer.get();
else
{
ch = _next;
_next = 0;
}
byte ch = buffer.get();
CharState s = __charState[0xff & ch];
switch(s)
@ -297,18 +277,11 @@ public class MultiPartParser
case CR:
if (_cr)
{
if (!_acceptCrAsLineTermination)
throw new BadMessageException("Bad EOL");
// TODO log compliance violation
return (byte)'\n';
}
throw new BadMessageException("Bad EOL");
_cr=true;
if (buffer.hasRemaining())
return getNextByte(buffer, last);
else if (_acceptCrAsLineTermination && last)
return '\n';
return getNextByte(buffer);
// Can return 0 here to indicate the need for more characters,
// because a real 0 in the buffer would cause a BadMessage below
@ -316,14 +289,8 @@ public class MultiPartParser
case LEGAL:
if (_cr)
{
if (!_acceptCrAsLineTermination)
throw new BadMessageException("Bad EOL");
// TODO log compliance violation
_next = ch;
_cr = false;
return (byte)'\n';
}
throw new BadMessageException("Bad EOL");
return ch;
case ILLEGAL:
@ -372,11 +339,11 @@ public class MultiPartParser
case DELIMITER:
case DELIMITER_PADDING:
case DELIMITER_CLOSE:
parseDelimiter(buffer, last);
parseDelimiter(buffer);
continue;
case BODY_PART:
handle = parseMimePartHeaders(buffer, last);
handle = parseMimePartHeaders(buffer);
break;
case FIRST_OCTETS:
@ -454,11 +421,11 @@ public class MultiPartParser
}
/* ------------------------------------------------------------------------------- */
private void parseDelimiter(ByteBuffer buffer, boolean last)
private void parseDelimiter(ByteBuffer buffer)
{
while (__delimiterStates.contains(_state) && hasNextByte(buffer))
{
byte b=getNextByte(buffer, last);
byte b=getNextByte(buffer);
if (b==0)
return;
@ -498,15 +465,22 @@ public class MultiPartParser
/*
* Parse the message headers and return true if the handler has signaled for a return
*/
protected boolean parseMimePartHeaders(ByteBuffer buffer, boolean last)
protected boolean parseMimePartHeaders(ByteBuffer buffer)
{
// Process headers
while (_state==State.BODY_PART && hasNextByte(buffer))
{
// process each character
byte b=getNextByte(buffer, last);
byte b=getNextByte(buffer);
if (b==0)
break;
if(b!=LINE_FEED)
_totalHeaderLineLength++;
if(_totalHeaderLineLength > _maxHeaderLineLength)
throw new IllegalStateException("Header Line Exceeded Max Length");
switch (_fieldState)
{
@ -641,6 +615,7 @@ public class MultiPartParser
{
_fieldValue=takeString();
_length=-1;
_totalHeaderLineLength=-1;
}
setState(FieldState.FIELD);
break;
@ -673,13 +648,6 @@ public class MultiPartParser
protected boolean parseOctetContent(ByteBuffer buffer)
{
//handle the next content that was held because of \r as \r\n
if (_next!=0)
{
_handler.content(BufferUtil.toBuffer(new byte[] {_next}),false);
_next = 0;
}
//Starts With
if (_partialBoundary>0)

View File

@ -90,11 +90,10 @@ public class MultiPartInputStreamTest
try
{
mpis.getParts();
fail ("Multipart incomplete");
fail ("Incomplete Multipart");
}
catch (IOException e)
{
System.err.println(e.getMessage());
assertTrue(e.getMessage().startsWith("Incomplete"));
}
}
@ -237,11 +236,11 @@ public class MultiPartInputStreamTest
try
{
mpis.getParts();
fail ("Multipart missing body");
fail ("Missing initial multi part boundary");
}
catch (IOException e)
{
assertTrue(e.getMessage().startsWith("Missing content"));
assertTrue(e.getMessage().contains("Missing initial multi part boundary"));
}
}
@ -305,11 +304,11 @@ public class MultiPartInputStreamTest
try
{
mpis.getParts();
fail ("Multipart missing body");
fail("Missing initial multi part boundary");
}
catch (IOException e)
{
assertTrue(e.getMessage().startsWith("Missing initial"));
assertTrue(e.getMessage().contains("Missing initial multi part boundary"));
}
}
@ -403,13 +402,9 @@ public class MultiPartInputStreamTest
Collection<Part> parts = mpis.getParts();
assertThat(parts, notNullValue());
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1");
assertThat(field1, notNullValue());
assertThat(parts.size(), is(1));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(field1.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("Joe Blow"));
Part stuff = mpis.getPart("stuff");
assertThat(stuff, notNullValue());
baos = new ByteArrayOutputStream();
@ -446,10 +441,10 @@ public class MultiPartInputStreamTest
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try
{
parts = mpis.getParts();
mpis.getParts();
fail("Request should have exceeded maxRequestSize");
}
catch (IllegalStateException e)
@ -534,6 +529,7 @@ public class MultiPartInputStreamTest
}
catch (IllegalStateException e)
{
e.printStackTrace();
assertTrue(e.getMessage().startsWith("Multipart Mime part"));
}
@ -600,12 +596,12 @@ public class MultiPartInputStreamTest
String str = "--AaB03x\n"+
"content-disposition: form-data; name=\"field1\"\n"+
"\n"+
"Joe Blow\n"+
"--AaB03x\n"+
"Joe Blow"+
"\r\n--AaB03x\n"+
"content-disposition: form-data; name=\"field2\"\n"+
"\n"+
"Other\n"+
"--AaB03x--\n";
"Other"+
"\r\n--AaB03x--\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
@ -620,12 +616,14 @@ public class MultiPartInputStreamTest
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Joe Blow"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
@Test
@ -648,22 +646,30 @@ public class MultiPartInputStreamTest
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Joe Blow"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
try
{
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Joe Blow"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
catch(Throwable e)
{
assertTrue(e.getMessage().contains("Bad EOL"));
}
}
@Test
@ -687,28 +693,37 @@ public class MultiPartInputStreamTest
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("\nJoe Blow\n"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
try
{
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("\nJoe Blow\n"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
catch(Throwable e)
{
assertTrue(e.getMessage().contains("Bad EOL"));
}
}
@Test
public void testBufferOverflowNoCRLF () throws Exception
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("--AaB03x".getBytes());
for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
baos.write("--AaB03x\r\n".getBytes());
for (int i=0; i< 3000; i++) //create content that will overrun default buffer size of BufferedInputStream
{
baos.write('a');
}
@ -722,11 +737,11 @@ public class MultiPartInputStreamTest
try
{
mpis.getParts();
fail ("Multipart buffer overrun");
fail ("Header Line Exceeded Max Length");
}
catch (IOException e)
catch (Throwable e)
{
assertTrue(e.getMessage().startsWith("Buffer size exceeded"));
assertTrue(e.getMessage().startsWith("Header Line Exceeded Max Length"));
}
}
@ -735,12 +750,12 @@ public class MultiPartInputStreamTest
public void testCharsetEncoding () throws Exception
{
String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1";
String str = "--TheBoundary\r"+
"content-disposition: form-data; name=\"field1\"\r"+
"\r"+
String str = "--TheBoundary\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
"\nJoe Blow\n"+
"\r"+
"--TheBoundary--\r";
"\r\n"+
"--TheBoundary--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
@ -907,8 +922,8 @@ public class MultiPartInputStreamTest
assertThat(stuff.getSize(),is(51L));
File tmpfile = ((MultiPartInputStreamParser.MultiPart)stuff).getFile();
assertThat(tmpfile,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(((MultiPartInputStreamParser.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
assertThat(tmpfile,notNullValue()); // longer than 50 bytes, should already be a tmp file
assertThat(stuff.getBytes(),nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(),is(true));
assertThat(tmpfile.getName(),is(not("stuff with space.txt")));
stuff.write(filename);
@ -1000,7 +1015,7 @@ public class MultiPartInputStreamTest
assertNotNull(p2);
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertEquals("hello jetty", baos.toString("US-ASCII"));
assertEquals(B64Code.encode("hello jetty"), baos.toString("US-ASCII"));
Part p3 = mpis.getPart("final");
assertNotNull(p3);
@ -1044,7 +1059,7 @@ public class MultiPartInputStreamTest
assertNotNull(p2);
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertEquals("truth=beauty", baos.toString("US-ASCII"));
assertEquals("truth=3Dbeauty", baos.toString("US-ASCII"));
}

View File

@ -462,20 +462,25 @@ public class MultiPartParserTest
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"AaB03x",true);
MultiPartParser parser = new MultiPartParser(handler,"AaB03x");
ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r"+
"content-disposition: form-data; name=\"field1\"\r"+
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r"+
"Joe Blow\r\n"+
"--AaB03x--\r");
"--AaB03x--\r\n");
/* Test Progression to END State */
parser.parse(data,true);
assertThat(parser.getState(), is(State.END));
assertThat(data.remaining(),is(0));
try
{
parser.parse(data,true);
fail("Invalid End of Line");
}
catch(BadMessageException e) {
assertTrue(e.getMessage().contains("Bad EOL"));
}
}

View File

@ -36,6 +36,7 @@ import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -44,6 +45,7 @@ import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import org.eclipse.jetty.util.ReadLineInputStream.Termination;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -69,8 +71,29 @@ public class MultiPartInputStreamParser
protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames;
EnumSet<NonCompliance> nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class);
public enum NonCompliance
{
CR_TERMINATION,
LF_TERMINATION,
NO_INITIAL_CRLF,
BASE64_TRANSFER_ENCODING,
QUOTED_PRINTABLE_TRANSFER_ENCODING
}
public EnumSet<NonCompliance> getNonComplianceWarnings()
{
EnumSet<Termination> term = ((ReadLineInputStream)_in).getLineTerminations();
if(term.contains(Termination.CR))
nonComplianceWarnings.add(NonCompliance.CR_TERMINATION);
if(term.contains(Termination.LF))
nonComplianceWarnings.add(NonCompliance.LF_TERMINATION);
return nonComplianceWarnings;
}
public class MultiPart implements Part
{
protected String _name;
@ -175,8 +198,8 @@ public class MultiPartInputStreamParser
_out.flush();
_bout.writeTo(bos);
_out.close();
_bout = null;
}
_bout = null;
_out = bos;
}
@ -563,6 +586,8 @@ public class MultiPartInputStreamParser
throw new IOException("Missing content for multipart request");
boolean badFormatLogged = false;
String untrimmed = line;
line=line.trim();
while (line != null && !line.equals(boundary) && !line.equals(lastBoundary))
{
@ -572,16 +597,22 @@ public class MultiPartInputStreamParser
badFormatLogged = true;
}
line=((ReadLineInputStream)_in).readLine();
line=(line==null?line:line.trim());
untrimmed = line;
if(line!=null)
line = line.trim();
}
if (line == null || line.length() == 0)
if (line == null || line.length() == 0)
throw new IOException("Missing initial multi part boundary");
// Empty multipart.
if (line.equals(lastBoundary))
return;
// check compliance of preamble
if (Character.isWhitespace(untrimmed.charAt(0)))
nonComplianceWarnings.add(NonCompliance.NO_INITIAL_CRLF);
// Read each part
boolean lastPart=false;
@ -671,10 +702,12 @@ public class MultiPartInputStreamParser
InputStream partInput = null;
if ("base64".equalsIgnoreCase(contentTransferEncoding))
{
nonComplianceWarnings.add(NonCompliance.BASE64_TRANSFER_ENCODING);
partInput = new Base64InputStream((ReadLineInputStream)_in);
}
else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
{
nonComplianceWarnings.add(NonCompliance.QUOTED_PRINTABLE_TRANSFER_ENCODING);
partInput = new FilterInputStream(_in)
{
@Override
@ -917,5 +950,8 @@ public class MultiPartInputStreamParser
return _buffer[_pos++];
}
}
}
}

View File

@ -22,6 +22,9 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance;
/**
* ReadLineInputStream
@ -32,6 +35,15 @@ public class ReadLineInputStream extends BufferedInputStream
{
boolean _seenCRLF;
boolean _skipLF;
private EnumSet<Termination> _lineTerminations = EnumSet.noneOf(Termination.class);
public EnumSet<Termination> getLineTerminations() { return _lineTerminations; }
public enum Termination
{
CRLF,
LF,
CR,
EOF
}
public ReadLineInputStream(InputStream in)
{
@ -54,13 +66,18 @@ public class ReadLineInputStream extends BufferedInputStream
if (markpos < 0)
throw new IOException("Buffer size exceeded: no line terminator");
if(_skipLF && b!='\n')
_lineTerminations.add(Termination.CR);
if (b==-1)
{
int m=markpos;
markpos=-1;
if (pos>m)
{
_lineTerminations.add(Termination.EOF);
return new String(buf,m,pos-m, StandardCharsets.UTF_8);
}
return null;
}
@ -72,10 +89,18 @@ public class ReadLineInputStream extends BufferedInputStream
if (_seenCRLF && pos<count)
{
if (buf[pos]=='\n')
{
_lineTerminations.add(Termination.CRLF);
pos+=1;
}
else
{
_lineTerminations.add(Termination.CR);
}
}
else
_skipLF=true;
int m=markpos;
markpos=-1;
return new String(buf,m,p-m-1,StandardCharsets.UTF_8);
@ -88,10 +113,12 @@ public class ReadLineInputStream extends BufferedInputStream
_skipLF=false;
_seenCRLF=true;
markpos++;
_lineTerminations.add(Termination.CRLF);
continue;
}
int m=markpos;
markpos=-1;
_lineTerminations.add(Termination.LF);
return new String(buf,m,pos-m-1,StandardCharsets.UTF_8);
}
}

View File

@ -36,6 +36,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement;
@ -45,6 +46,7 @@ import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import org.eclipse.jetty.util.MultiPartInputStreamParser.MultiPart;
import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance;
import org.hamcrest.Matchers;
import org.junit.Test;
@ -119,6 +121,7 @@ public class MultiPartInputStreamTest
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertTrue(mpis.getParts().isEmpty());
assertEquals(EnumSet.noneOf(NonCompliance.class), mpis.getNonComplianceWarnings());
}
@ -141,6 +144,7 @@ public class MultiPartInputStreamTest
_tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty());
assertEquals(EnumSet.noneOf(NonCompliance.class), mpis.getNonComplianceWarnings());
}
@Test
@ -201,7 +205,10 @@ public class MultiPartInputStreamTest
assertThat(title, notNullValue());
assertThat(title.getSize(), is(3L));
IO.copy(title.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("ttt"));
assertThat(baos.toString("US-ASCII"), is("ttt"));
assertEquals(EnumSet.noneOf(NonCompliance.class), mpis.getNonComplianceWarnings());
}
@Test
@ -215,6 +222,7 @@ public class MultiPartInputStreamTest
_tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty());
assertEquals(EnumSet.noneOf(NonCompliance.class), mpis.getNonComplianceWarnings());
}
@Test
@ -369,10 +377,11 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("aaaa"));
assertEquals(EnumSet.of(NonCompliance.LF_TERMINATION), mpis.getNonComplianceWarnings());
}
@Test
public void testLeadingWhitespaceBodyWithoutCRLF()
throws Exception
@ -410,13 +419,12 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("bbbbb"));
assertEquals(EnumSet.of(NonCompliance.NO_INITIAL_CRLF), mpis.getNonComplianceWarnings());
}
@Test
public void testNoLimits()
throws Exception
@ -431,6 +439,7 @@ public class MultiPartInputStreamTest
assertFalse(parts.isEmpty());
}
@Test
public void testRequestTooBig ()
throws Exception
@ -621,6 +630,8 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
assertEquals(EnumSet.of(NonCompliance.LF_TERMINATION), mpis.getNonComplianceWarnings());
}
@Test
@ -659,6 +670,8 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
assertEquals(EnumSet.of(NonCompliance.CR_TERMINATION), mpis.getNonComplianceWarnings());
}
@Test
@ -696,6 +709,8 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
assertEquals(EnumSet.of(NonCompliance.CR_TERMINATION), mpis.getNonComplianceWarnings());
}
@Test
@ -745,6 +760,7 @@ public class MultiPartInputStreamTest
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
}
@ -1002,6 +1018,8 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(p3.getInputStream(), baos);
assertEquals("the end", baos.toString("US-ASCII"));
assertEquals(EnumSet.of(NonCompliance.BASE64_TRANSFER_ENCODING), mpis.getNonComplianceWarnings());
}
@Test
@ -1040,6 +1058,8 @@ public class MultiPartInputStreamTest
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertEquals("truth=beauty", baos.toString("US-ASCII"));
assertEquals(EnumSet.of(NonCompliance.QUOTED_PRINTABLE_TRANSFER_ENCODING), mpis.getNonComplianceWarnings());
}

View File

@ -22,8 +22,10 @@ import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.ReadLineInputStream.Termination;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -101,6 +103,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CR), _in.getLineTerminations());
}
@Test
@ -114,6 +117,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.LF), _in.getLineTerminations());
}
@Test
@ -127,6 +131,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CRLF), _in.getLineTerminations());
}
@ -145,6 +150,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CR), _in.getLineTerminations());
}
@Test
@ -162,6 +168,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.LF), _in.getLineTerminations());
}
@Test
@ -180,6 +187,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("World",_in.readLine());
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CRLF), _in.getLineTerminations());
}
@ -201,6 +209,7 @@ public class ReadLineInputStreamTest
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.LF), _in.getLineTerminations());
}
@Test
@ -221,6 +230,8 @@ public class ReadLineInputStreamTest
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CR), _in.getLineTerminations());
}
@Test
@ -241,6 +252,8 @@ public class ReadLineInputStreamTest
Assert.assertEquals("",_in.readLine());
Assert.assertEquals(null,_in.readLine());
Assert.assertEquals(EnumSet.of(Termination.CRLF), _in.getLineTerminations());
}