diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 30d8fc9d974..55f4c1ef37a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -447,7 +447,7 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ enum CharState { ILLEGAL, CR, LF, LEGAL } - private final static CharState[] __charState; + public final static CharState[] TOKEN_CHAR; static { // token = 1*tchar @@ -462,38 +462,38 @@ public class HttpParser // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) - __charState=new CharState[256]; - Arrays.fill(__charState,CharState.ILLEGAL); - __charState[LINE_FEED]=CharState.LF; - __charState[CARRIAGE_RETURN]=CharState.CR; - __charState[TAB]=CharState.LEGAL; - __charState[SPACE]=CharState.LEGAL; + TOKEN_CHAR =new CharState[256]; + Arrays.fill(TOKEN_CHAR,CharState.ILLEGAL); + TOKEN_CHAR[LINE_FEED]=CharState.LF; + TOKEN_CHAR[CARRIAGE_RETURN]=CharState.CR; + TOKEN_CHAR[TAB]=CharState.LEGAL; + TOKEN_CHAR[SPACE]=CharState.LEGAL; - __charState['!']=CharState.LEGAL; - __charState['#']=CharState.LEGAL; - __charState['$']=CharState.LEGAL; - __charState['%']=CharState.LEGAL; - __charState['&']=CharState.LEGAL; - __charState['\'']=CharState.LEGAL; - __charState['*']=CharState.LEGAL; - __charState['+']=CharState.LEGAL; - __charState['-']=CharState.LEGAL; - __charState['.']=CharState.LEGAL; - __charState['^']=CharState.LEGAL; - __charState['_']=CharState.LEGAL; - __charState['`']=CharState.LEGAL; - __charState['|']=CharState.LEGAL; - __charState['~']=CharState.LEGAL; + TOKEN_CHAR['!']=CharState.LEGAL; + TOKEN_CHAR['#']=CharState.LEGAL; + TOKEN_CHAR['$']=CharState.LEGAL; + TOKEN_CHAR['%']=CharState.LEGAL; + TOKEN_CHAR['&']=CharState.LEGAL; + TOKEN_CHAR['\'']=CharState.LEGAL; + TOKEN_CHAR['*']=CharState.LEGAL; + TOKEN_CHAR['+']=CharState.LEGAL; + TOKEN_CHAR['-']=CharState.LEGAL; + TOKEN_CHAR['.']=CharState.LEGAL; + TOKEN_CHAR['^']=CharState.LEGAL; + TOKEN_CHAR['_']=CharState.LEGAL; + TOKEN_CHAR['`']=CharState.LEGAL; + TOKEN_CHAR['|']=CharState.LEGAL; + TOKEN_CHAR['~']=CharState.LEGAL; - __charState['"']=CharState.LEGAL; + TOKEN_CHAR['"']=CharState.LEGAL; - __charState['\\']=CharState.LEGAL; - __charState['(']=CharState.LEGAL; - __charState[')']=CharState.LEGAL; - Arrays.fill(__charState,0x21,0x27+1,CharState.LEGAL); - Arrays.fill(__charState,0x2A,0x5B+1,CharState.LEGAL); - Arrays.fill(__charState,0x5D,0x7E+1,CharState.LEGAL); - Arrays.fill(__charState,0x80,0xFF+1,CharState.LEGAL); + TOKEN_CHAR['\\']=CharState.LEGAL; + TOKEN_CHAR['(']=CharState.LEGAL; + TOKEN_CHAR[')']=CharState.LEGAL; + Arrays.fill(TOKEN_CHAR,0x21,0x27+1,CharState.LEGAL); + Arrays.fill(TOKEN_CHAR,0x2A,0x5B+1,CharState.LEGAL); + Arrays.fill(TOKEN_CHAR,0x5D,0x7E+1,CharState.LEGAL); + Arrays.fill(TOKEN_CHAR,0x80,0xFF+1,CharState.LEGAL); } @@ -502,7 +502,7 @@ public class HttpParser { byte ch = buffer.get(); - CharState s = __charState[0xff & ch]; + CharState s = TOKEN_CHAR[0xff & ch]; switch(s) { case ILLEGAL: diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java index 5fb4d85bd15..8e68ba43c9d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java @@ -52,28 +52,27 @@ import org.eclipse.jetty.util.log.Logger; /** * MultiPartInputStream - * + *

* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings. - * + * * @see https://tools.ietf.org/html/rfc7578 */ public class MultiPartFormInputStream { private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.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 EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); - protected InputStream _in; - protected MultipartConfigElement _config; - protected String _contentType; - protected MultiMap _parts; - protected Throwable _err; - protected File _tmpDir; - protected File _contextTmpDir; - protected boolean _deleteOnExit; - protected boolean _writeFilesWithFilenames; - protected boolean _parsed; - + private static final MultiMap EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); + private InputStream _in; + private MultipartConfigElement _config; + private String _contentType; + private MultiMap _parts; + private Throwable _err; + private File _tmpDir; + private File _contextTmpDir; + private boolean _deleteOnExit; + private boolean _writeFilesWithFilenames; + private boolean _parsed; + private int _bufferSize = 16 * 1024; + public class MultiPart implements Part { protected String _name; @@ -85,24 +84,24 @@ public class MultiPartFormInputStream protected MultiMap _headers; protected long _size = 0; protected boolean _temporary = true; - - public MultiPart(String name, String filename) throws IOException + + public MultiPart(String name, String filename) { _name = name; _filename = filename; } - + @Override public String toString() { - return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,tmp=%b,file=%s}",_name,_filename,_contentType,_size,_temporary,_file); + return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,tmp=%b,file=%s}", _name, _filename, _contentType, _size, _temporary, _file); } - + protected void setContentType(String contentType) { _contentType = contentType; } - + protected void open() throws IOException { // We will either be writing to a file, if it has a filename on the content-disposition @@ -119,38 +118,38 @@ public class MultiPartFormInputStream _out = _bout = new ByteArrayOutputStream2(); } } - + protected void close() throws IOException { _out.close(); } - + protected void write(int b) throws IOException { if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize()) throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); - + if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null) createFile(); - + _out.write(b); _size++; } - + protected void write(byte[] bytes, int offset, int length) throws IOException { if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize()) throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); - + if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null) createFile(); - - _out.write(bytes,offset,length); + + _out.write(bytes, offset, length); _size += length; } - + protected void createFile() throws IOException { /* @@ -158,16 +157,16 @@ public class MultiPartFormInputStream */ final boolean USER = true; final boolean WORLD = false; - - _file = File.createTempFile("MultiPart","",MultiPartFormInputStream.this._tmpDir); - _file.setReadable(false,WORLD); // (reset) disable it for everyone first - _file.setReadable(true,USER); // enable for user only - + + _file = File.createTempFile("MultiPart", "", MultiPartFormInputStream.this._tmpDir); + _file.setReadable(false, WORLD); // (reset) disable it for everyone first + _file.setReadable(true, USER); // enable for user only + if (_deleteOnExit) _file.deleteOnExit(); FileOutputStream fos = new FileOutputStream(_file); BufferedOutputStream bos = new BufferedOutputStream(fos); - + if (_size > 0 && _out != null) { // already written some bytes, so need to copy them into the file @@ -178,53 +177,38 @@ public class MultiPartFormInputStream _bout = null; _out = bos; } - + protected void setHeaders(MultiMap headers) { _headers = headers; } - - /** - * @see javax.servlet.http.Part#getContentType() - */ + @Override public String getContentType() { return _contentType; } - - /** - * @see javax.servlet.http.Part#getHeader(java.lang.String) - */ + @Override public String getHeader(String name) { if (name == null) return null; - return _headers.getValue(StringUtil.asciiToLowerCase(name),0); + return _headers.getValue(StringUtil.asciiToLowerCase(name), 0); } - - /** - * @see javax.servlet.http.Part#getHeaderNames() - */ + @Override public Collection getHeaderNames() { return _headers.keySet(); } - - /** - * @see javax.servlet.http.Part#getHeaders(java.lang.String) - */ + @Override public Collection getHeaders(String name) { return _headers.getValues(name); } - - /** - * @see javax.servlet.http.Part#getInputStream() - */ + @Override public InputStream getInputStream() throws IOException { @@ -236,68 +220,52 @@ public class MultiPartFormInputStream else { // part content is in memory - return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); + return new ByteArrayInputStream(_bout.getBuf(), 0, _bout.size()); } } - - /** - * @see javax.servlet.http.Part#getSubmittedFileName() - */ + @Override public String getSubmittedFileName() { return getContentDispositionFilename(); } - + public byte[] getBytes() { if (_bout != null) return _bout.toByteArray(); return null; } - - /** - * @see javax.servlet.http.Part#getName() - */ + @Override public String getName() { return _name; } - - /** - * @see javax.servlet.http.Part#getSize() - */ + @Override public long getSize() { return _size; } - - /** - * @see javax.servlet.http.Part#write(java.lang.String) - */ + @Override public void write(String fileName) throws IOException { if (_file == null) { _temporary = false; - + // part data is only in the ByteArrayOutputStream and never been written to disk - _file = new File(_tmpDir,fileName); - - BufferedOutputStream bos = null; - try + _file = new File(_tmpDir, fileName); + + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(_file))) { - bos = new BufferedOutputStream(new FileOutputStream(_file)); _bout.writeTo(bos); bos.flush(); } finally { - if (bos != null) - bos.close(); _bout = null; } } @@ -305,51 +273,50 @@ public class MultiPartFormInputStream { // the part data is already written to a temporary file, just rename it _temporary = false; - + Path src = _file.toPath(); Path target = src.resolveSibling(fileName); - Files.move(src,target,StandardCopyOption.REPLACE_EXISTING); + Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); _file = target.toFile(); } } - + /** * Remove the file, whether or not Part.write() was called on it (ie no longer temporary) - * - * @see javax.servlet.http.Part#delete() */ @Override public void delete() throws IOException { if (_file != null && _file.exists()) - _file.delete(); + if (!_file.delete()) + throw new IOException("Could Not Delete File"); } - + /** * Only remove tmp files. * - * @throws IOException - * if unable to delete the file + * @throws IOException if unable to delete the file */ public void cleanUp() throws IOException { if (_temporary && _file != null && _file.exists()) - _file.delete(); + if (!_file.delete()) + throw new IOException("Could Not Delete File"); } - + /** * Get the file - * + * * @return the file, if any, the data has been written to. */ public File getFile() { return _file; } - + /** * Get the filename from the content-disposition. - * + * * @return null or the filename */ public String getContentDispositionFilename() @@ -357,16 +324,12 @@ public class MultiPartFormInputStream return _filename; } } - + /** - * @param in - * Request input stream - * @param contentType - * Content-Type header - * @param config - * MultipartConfigElement - * @param contextTmpDir - * javax.servlet.context.tempdir + * @param in Request input stream + * @param contentType Content-Type header + * @param config MultipartConfigElement + * @param contextTmpDir javax.servlet.context.tempdir */ public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) { @@ -375,10 +338,10 @@ public class MultiPartFormInputStream _contextTmpDir = contextTmpDir; if (_contextTmpDir == null) _contextTmpDir = new File(System.getProperty("java.io.tmpdir")); - + if (_config == null) _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); - + if (in instanceof ServletInputStream) { if (((ServletInputStream)in).isFinished()) @@ -390,7 +353,7 @@ public class MultiPartFormInputStream } _in = new BufferedInputStream(in); } - + /** * @return whether the list of parsed parts is empty */ @@ -402,16 +365,16 @@ public class MultiPartFormInputStream Collection> values = _parts.values(); for (List partList : values) { - if(partList.size() != 0) + if (partList.size() != 0) return false; } - + return true; } /** * Get the already parsed parts. - * + * * @return the parts that were parsed */ @Deprecated @@ -419,25 +382,25 @@ public class MultiPartFormInputStream { if (_parts == null) return Collections.emptyList(); - + Collection> values = _parts.values(); List parts = new ArrayList<>(); for (List o : values) { - List asList = LazyList.getList(o,false); + List asList = LazyList.getList(o, false); parts.addAll(asList); } return parts; } - + /** * Delete any tmp storage for parts, and clear out the parts list. */ public void deleteParts() { if (!_parsed) - return; - + return; + Collection parts; try { @@ -447,8 +410,8 @@ public class MultiPartFormInputStream { throw new RuntimeException(e); } - MultiException err = new MultiException(); + MultiException err = null; for (Part p : parts) { try @@ -457,59 +420,58 @@ public class MultiPartFormInputStream } catch (Exception e) { + if (err == null) + err = new MultiException(); err.add(e); } } _parts.clear(); - - err.ifExceptionThrowRuntime(); + + if (err != null) + err.ifExceptionThrowRuntime(); } - + /** * Parse, if necessary, the multipart data and return the list of Parts. * * @return the parts - * @throws IOException - * if unable to get the parts + * @throws IOException if unable to get the parts */ public Collection getParts() throws IOException { if (!_parsed) parse(); throwIfError(); - + Collection> values = _parts.values(); List parts = new ArrayList<>(); for (List o : values) { - List asList = LazyList.getList(o,false); + List asList = LazyList.getList(o, false); parts.addAll(asList); } return parts; } - + /** * Get the named Part. * - * @param name - * the part name + * @param name the part name * @return the parts - * @throws IOException - * if unable to get the part + * @throws IOException if unable to get the part */ public Part getPart(String name) throws IOException { - if(!_parsed) + if (!_parsed) parse(); throwIfError(); - return _parts.getValue(name,0); + return _parts.getValue(name, 0); } - + /** * Throws an exception if one has been latched. - * - * @throws IOException - * the exception (if present) + * + * @throws IOException the exception (if present) */ protected void throwIfError() throws IOException { @@ -523,10 +485,9 @@ public class MultiPartFormInputStream throw new IllegalStateException(_err); } } - + /** * Parse, if necessary, the multipart stream. - * */ protected void parse() { @@ -537,14 +498,13 @@ public class MultiPartFormInputStream try { - // 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; @@ -556,70 +516,67 @@ public class MultiPartFormInputStream if (f.isAbsolute()) _tmpDir = f; else - _tmpDir = new File(_contextTmpDir,_config.getLocation()); + _tmpDir = new File(_contextTmpDir, _config.getLocation()); } - + if (!_tmpDir.exists()) _tmpDir.mkdirs(); - + String contentTypeBoundary = ""; int bstart = _contentType.indexOf("boundary="); if (bstart >= 0) { - int bend = _contentType.indexOf(";",bstart); - bend = (bend < 0?_contentType.length():bend); - contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim()); + int bend = _contentType.indexOf(";", bstart); + bend = (bend < 0 ? _contentType.length() : bend); + 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 // + MultiPartParser parser = new MultiPartParser(handler, contentTypeBoundary); + 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 - */ + int len; long total = 0; - + while (true) { - + len = _in.read(data); - + if (len > 0) { + + // keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize 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); - if (parser.parse(buffer,false)) + if (parser.parse(buffer, false)) break; - if(buffer.hasRemaining()) + if (buffer.hasRemaining()) throw new IllegalStateException("Buffer did not fully consume"); - + } else if (len == -1) { - parser.parse(BufferUtil.EMPTY_BUFFER,true); + 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) { @@ -628,39 +585,37 @@ public class MultiPartFormInputStream else _err = new IOException("Incomplete Multipart"); } - + if (LOG.isDebugEnabled()) { - LOG.debug("Parsing Complete {} err={}",parser,_err); + LOG.debug("Parsing Complete {} err={}", parser, _err); } - + } catch (Throwable e) { _err = e; - return; } - } - + class Handler implements MultiPartParser.Handler { private MultiPart _part = null; private String contentDisposition = null; private String contentType = null; private MultiMap 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(StringUtil.asciiToLowerCase(key),value); + headers.put(StringUtil.asciiToLowerCase(key), value); if (key.equalsIgnoreCase("content-disposition")) contentDisposition = value; else if (key.equalsIgnoreCase("content-type")) @@ -668,27 +623,27 @@ public class MultiPartFormInputStream // Transfer encoding is not longer considers as it is deprecated as per // https://tools.ietf.org/html/rfc7578#section-4.7 - + } - + @Override public boolean headerComplete() { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) { - LOG.debug("headerComplete {}",this); + LOG.debug("headerComplete {}", this); } try - { + { // Extract content-disposition boolean form_data = false; if (contentDisposition == null) { throw new IOException("Missing content-disposition"); } - - QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true); + + QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition, ";", false, true); String name = null; String filename = null; while (tok.hasMoreTokens()) @@ -702,7 +657,7 @@ public class MultiPartFormInputStream else if (tl.startsWith("filename=")) filename = filenameValue(t); } - + // Check disposition if (!form_data) throw new IOException("Part not form-data"); @@ -714,13 +669,13 @@ public class MultiPartFormInputStream // have not yet seen a name field. if (name == null) throw new IOException("No name in part"); - - + + // create the new part - _part = new MultiPart(name,filename); + _part = new MultiPart(name, filename); _part.setHeaders(headers); _part.setContentType(contentType); - _parts.add(name,_part); + _parts.add(name, _part); try { @@ -737,21 +692,21 @@ public class MultiPartFormInputStream _err = e; return true; } - + return false; } - + @Override public boolean content(ByteBuffer buffer, boolean last) - { - if(_part == null) + { + if (_part == null) return false; if (BufferUtil.hasContent(buffer)) { try { - _part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + _part.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } catch (IOException e) { @@ -759,7 +714,7 @@ public class MultiPartFormInputStream return true; } } - + if (last) { try @@ -772,12 +727,12 @@ public class MultiPartFormInputStream return true; } } - + return false; } - + @Override - public void startPart() + public void startPart() { reset(); } @@ -786,9 +741,9 @@ public class MultiPartFormInputStream public void earlyEOF() { if (LOG.isDebugEnabled()) - LOG.debug("Early EOF {}",MultiPartFormInputStream.this); + LOG.debug("Early EOF {}", MultiPartFormInputStream.this); } - + public void reset() { _part = null; @@ -797,41 +752,41 @@ public class MultiPartFormInputStream headers = new MultiMap<>(); } } - + public void setDeleteOnExit(boolean deleteOnExit) { _deleteOnExit = deleteOnExit; } - + public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames) { _writeFilesWithFilenames = writeFilesWithFilenames; } - + public boolean isWriteFilesWithFilenames() { return _writeFilesWithFilenames; } - + public boolean isDeleteOnExit() { return _deleteOnExit; } - + /* ------------------------------------------------------------ */ - private String value(String nameEqualsValue) + private static String value(String nameEqualsValue) { int idx = nameEqualsValue.indexOf('='); String value = nameEqualsValue.substring(idx + 1).trim(); return QuotedStringTokenizer.unquoteOnly(value); } - + /* ------------------------------------------------------------ */ - private String filenameValue(String nameEqualsValue) + private static String filenameValue(String nameEqualsValue) { int idx = nameEqualsValue.indexOf('='); String value = nameEqualsValue.substring(idx + 1).trim(); - + if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) { // incorrectly escaped IE filenames that have the whole path @@ -841,8 +796,8 @@ public class MultiPartFormInputStream value = value.substring(1); char last = value.charAt(value.length() - 1); if (last == '"' || last == '\'') - value = value.substring(0,value.length() - 1); - + value = value.substring(0, value.length() - 1); + return value; } else @@ -850,7 +805,22 @@ public class MultiPartFormInputStream // form a valid escape sequence to remain as many browsers // even on *nix systems will not escape a filename containing // backslashes - return QuotedStringTokenizer.unquoteOnly(value,true); + return QuotedStringTokenizer.unquoteOnly(value, true); + } + + /** + * @return the size of buffer used to read data from the input stream + */ + public int getBufferSize() + { + return _bufferSize; + } + + /** + * @param bufferSize the size of buffer used to read data from the input stream + */ + public void setBufferSize(int bufferSize) + { + _bufferSize = bufferSize; } - } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartParser.java index 5554e64094a..92ece5b07b5 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartParser.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.EnumSet; import org.eclipse.jetty.http.HttpParser.RequestHandler; @@ -31,202 +30,146 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ -/** A parser for MultiPart content type. - * + +/** + * A parser for MultiPart content type. + * * @see https://tools.ietf.org/html/rfc2046#section-5.1 * @see https://tools.ietf.org/html/rfc2045 */ public class MultiPartParser { 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)';'; - + + private static final byte COLON = (byte)':'; + private static final byte TAB = 0x09; + private static final byte LINE_FEED = 0x0A; + private static final byte CARRIAGE_RETURN = 0x0D; + private static final byte SPACE = 0x20; + // States public enum FieldState { FIELD, IN_NAME, - AFTER_NAME, + AFTER_NAME, VALUE, IN_VALUE } - + // States public enum State { PREAMBLE, DELIMITER, - DELIMITER_PADDING, + DELIMITER_PADDING, DELIMITER_CLOSE, BODY_PART, - FIRST_OCTETS, + FIRST_OCTETS, OCTETS, - EPILOGUE, + EPILOGUE, END } - - private final static EnumSet __delimiterStates = EnumSet.of(State.DELIMITER,State.DELIMITER_CLOSE,State.DELIMITER_PADDING); - + + private final static EnumSet __delimiterStates = EnumSet.of(State.DELIMITER, State.DELIMITER_CLOSE, State.DELIMITER_PADDING); + private final static int MAX_HEADER_LINE_LENGTH = 998; + private final boolean DEBUG = LOG.isDebugEnabled(); private final Handler _handler; private final SearchPattern _delimiterSearch; - + private String _fieldName; private String _fieldValue; - + private State _state = State.PREAMBLE; private FieldState _fieldState = FieldState.FIELD; private int _partialBoundary = 2; // No CRLF if no preamble private boolean _cr; private ByteBuffer _patternBuffer; - + private final Utf8StringBuilder _string = new Utf8StringBuilder(); private int _length; - + private int _totalHeaderLineLength = -1; - private int _maxHeaderLineLength = 998; - + /* ------------------------------------------------------------------------------- */ public MultiPartParser(Handler handler, String boundary) { _handler = handler; - + String delimiter = "\r\n--" + boundary; _patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII)); _delimiterSearch = SearchPattern.compile(_patternBuffer.array()); } - + public void reset() { _state = State.PREAMBLE; _fieldState = FieldState.FIELD; _partialBoundary = 2; // No CRLF if no preamble } - + /* ------------------------------------------------------------------------------- */ public Handler getHandler() { return _handler; } - + /* ------------------------------------------------------------------------------- */ public State getState() { return _state; } - + /* ------------------------------------------------------------------------------- */ public boolean isState(State state) { return _state == state; } - + /* ------------------------------------------------------------------------------- */ - enum CharState - { - ILLEGAL, CR, LF, LEGAL - } - private final static CharState[] __charState; - static - { - // token = 1*tchar - // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" - // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" - // / DIGIT / ALPHA - // ; any VCHAR, except delimiters - // quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE - // qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text - // obs-text = %x80-FF - // comment = "(" *( ctext / quoted-pair / comment ) ")" - // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text - // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) - - __charState = new CharState[256]; - Arrays.fill(__charState,CharState.ILLEGAL); - __charState[LINE_FEED] = CharState.LF; - __charState[CARRIAGE_RETURN] = CharState.CR; - __charState[TAB] = CharState.LEGAL; - __charState[SPACE] = CharState.LEGAL; - - __charState['!'] = CharState.LEGAL; - __charState['#'] = CharState.LEGAL; - __charState['$'] = CharState.LEGAL; - __charState['%'] = CharState.LEGAL; - __charState['&'] = CharState.LEGAL; - __charState['\''] = CharState.LEGAL; - __charState['*'] = CharState.LEGAL; - __charState['+'] = CharState.LEGAL; - __charState['-'] = CharState.LEGAL; - __charState['.'] = CharState.LEGAL; - __charState['^'] = CharState.LEGAL; - __charState['_'] = CharState.LEGAL; - __charState['`'] = CharState.LEGAL; - __charState['|'] = CharState.LEGAL; - __charState['~'] = CharState.LEGAL; - - __charState['"'] = CharState.LEGAL; - - __charState['\\'] = CharState.LEGAL; - __charState['('] = CharState.LEGAL; - __charState[')'] = CharState.LEGAL; - Arrays.fill(__charState,0x21,0x27 + 1,CharState.LEGAL); - Arrays.fill(__charState,0x2A,0x5B + 1,CharState.LEGAL); - Arrays.fill(__charState,0x5D,0x7E + 1,CharState.LEGAL); - Arrays.fill(__charState,0x80,0xFF + 1,CharState.LEGAL); - - } - - /* ------------------------------------------------------------------------------- */ - private boolean hasNextByte(ByteBuffer buffer) + private static boolean hasNextByte(ByteBuffer buffer) { return BufferUtil.hasContent(buffer); } - + /* ------------------------------------------------------------------------------- */ private byte getNextByte(ByteBuffer buffer) { - + byte ch = buffer.get(); - - CharState s = __charState[0xff & ch]; + + HttpParser.CharState s = HttpParser.TOKEN_CHAR[0xff & ch]; switch (s) { case LF: _cr = false; return ch; - + case CR: if (_cr) throw new BadMessageException("Bad EOL"); - + _cr = true; if (buffer.hasRemaining()) 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 return 0; - + case LEGAL: if (_cr) throw new BadMessageException("Bad EOL"); - + return ch; - + case ILLEGAL: default: - throw new IllegalCharacterException(_state,ch,buffer); + throw new IllegalCharacterException(_state, ch, buffer); } } - + /* ------------------------------------------------------------------------------- */ private void setString(String s) { @@ -234,7 +177,7 @@ public class MultiPartParser _string.append(s); _length = s.length(); } - + /* ------------------------------------------------------------------------------- */ /* * Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1 @@ -243,91 +186,92 @@ public class MultiPartParser { String s = _string.toString(); // trim trailing whitespace. - if (s.length()>_length) - s = s.substring(0,_length); + if (s.length() > _length) + s = s.substring(0, _length); _string.reset(); _length = -1; return s; } - + /* ------------------------------------------------------------------------------- */ + /** * Parse until next Event. - * + * * @param buffer the buffer to parse - * @param last whether this buffer contains last bit of content + * @param last whether this buffer contains last bit of content * @return True if an {@link RequestHandler} method was called and it returned true; */ public boolean parse(ByteBuffer buffer, boolean last) { boolean handle = false; - while (handle == false && BufferUtil.hasContent(buffer)) + while (!handle && BufferUtil.hasContent(buffer)) { switch (_state) { case PREAMBLE: parsePreamble(buffer); continue; - + case DELIMITER: case DELIMITER_PADDING: case DELIMITER_CLOSE: parseDelimiter(buffer); continue; - + case BODY_PART: handle = parseMimePartHeaders(buffer); break; - + case FIRST_OCTETS: case OCTETS: handle = parseOctetContent(buffer); break; - + case EPILOGUE: BufferUtil.clear(buffer); break; - + case END: handle = true; break; - + default: throw new IllegalStateException(); - + } } - + if (last && BufferUtil.isEmpty(buffer)) { if (_state == State.EPILOGUE) { _state = State.END; - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("messageComplete {}", this); return _handler.messageComplete(); } else { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("earlyEOF {}", this); _handler.earlyEOF(); return true; } } - + return handle; } - + /* ------------------------------------------------------------------------------- */ private void parsePreamble(ByteBuffer buffer) { if (_partialBoundary > 0) { - int partial = _delimiterSearch.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 == _delimiterSearch.getLength()) @@ -337,29 +281,27 @@ public class MultiPartParser setState(State.DELIMITER); return; } - + _partialBoundary = partial; BufferUtil.clear(buffer); return; } - + _partialBoundary = 0; } - - int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + + int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); if (delimiter >= 0) { buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength()); setState(State.DELIMITER); return; } - - _partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + + _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); BufferUtil.clear(buffer); - - return; } - + /* ------------------------------------------------------------------------------- */ private void parseDelimiter(ByteBuffer buffer) { @@ -368,18 +310,18 @@ public class MultiPartParser byte b = getNextByte(buffer); if (b == 0) return; - + if (b == '\n') { setState(State.BODY_PART); - if(LOG.isDebugEnabled()) - LOG.debug("startPart {}",this); + if (LOG.isDebugEnabled()) + LOG.debug("startPart {}", this); _handler.startPart(); return; } - + switch (_state) { case DELIMITER: @@ -388,7 +330,7 @@ public class MultiPartParser else setState(State.DELIMITER_PADDING); continue; - + case DELIMITER_CLOSE: if (b == '-') { @@ -397,14 +339,13 @@ public class MultiPartParser } setState(State.DELIMITER_PADDING); continue; - + case DELIMITER_PADDING: default: - continue; } } } - + /* ------------------------------------------------------------------------------- */ /* * Parse the message headers and return true if the handler has signaled for a return @@ -418,13 +359,13 @@ public class MultiPartParser byte b = getNextByte(buffer); if (b == 0) break; - + if (b != LINE_FEED) _totalHeaderLineLength++; - - if (_totalHeaderLineLength > _maxHeaderLineLength) + + if (_totalHeaderLineLength > MAX_HEADER_LINE_LENGTH) throw new IllegalStateException("Header Line Exceeded Max Length"); - + switch (_fieldState) { case FIELD: @@ -434,10 +375,10 @@ public class MultiPartParser case TAB: { // Folded field value! - + if (_fieldName == null) throw new IllegalStateException("First field folded"); - + if (_fieldValue == null) { _string.reset(); @@ -453,26 +394,26 @@ public class MultiPartParser setState(FieldState.VALUE); break; } - + case LINE_FEED: { handleField(); setState(State.FIRST_OCTETS); _partialBoundary = 2; // CRLF is option for empty parts - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("headerComplete {}", this); if (_handler.headerComplete()) return true; break; } - + default: { // process previous header handleField(); - + // New header setState(FieldState.IN_NAME); _string.reset(); @@ -481,7 +422,7 @@ public class MultiPartParser } } break; - + case IN_NAME: switch (b) { @@ -490,29 +431,29 @@ public class MultiPartParser _length = -1; setState(FieldState.VALUE); break; - + case SPACE: // Ignore trailing whitespaces setState(FieldState.AFTER_NAME); break; - + case LINE_FEED: { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("Line Feed in Name {}", this); handleField(); setState(FieldState.FIELD); break; } - + default: _string.append(b); _length = _string.length(); break; } break; - + case AFTER_NAME: switch (b) { @@ -521,22 +462,22 @@ public class MultiPartParser _length = -1; setState(FieldState.VALUE); break; - + case LINE_FEED: _fieldName = takeString(); _string.reset(); _fieldValue = ""; _length = -1; break; - + case SPACE: break; - + default: - throw new IllegalCharacterException(_state,b,buffer); + throw new IllegalCharacterException(_state, b, buffer); } break; - + case VALUE: switch (b) { @@ -544,14 +485,14 @@ public class MultiPartParser _string.reset(); _fieldValue = ""; _length = -1; - + setState(FieldState.FIELD); break; - + case SPACE: case TAB: break; - + default: _string.append(b); _length = _string.length(); @@ -559,14 +500,14 @@ public class MultiPartParser break; } break; - + case IN_VALUE: switch (b) { case SPACE: _string.append(b); break; - + case LINE_FEED: if (_length > 0) { @@ -576,7 +517,7 @@ public class MultiPartParser } setState(FieldState.FIELD); break; - + default: _string.append(b); if (b > SPACE || b < 0) @@ -584,35 +525,35 @@ public class MultiPartParser break; } break; - + default: throw new IllegalStateException(_state.toString()); - + } } return false; } - + /* ------------------------------------------------------------------------------- */ private void handleField() { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this); if (_fieldName != null && _fieldValue != null) - _handler.parsedField(_fieldName,_fieldValue); + _handler.parsedField(_fieldName, _fieldValue); _fieldName = _fieldValue = null; } - + /* ------------------------------------------------------------------------------- */ - + protected boolean parseOctetContent(ByteBuffer buffer) { - + // Starts With if (_partialBoundary > 0) { - int partial = _delimiterSearch.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 == _delimiterSearch.getLength()) @@ -621,12 +562,12 @@ public class MultiPartParser setState(State.DELIMITER); _partialBoundary = 0; - if(LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER),true,this); + if (LOG.isDebugEnabled()) + LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER), true, this); - return _handler.content(BufferUtil.EMPTY_BUFFER,true); + return _handler.content(BufferUtil.EMPTY_BUFFER, true); } - + _partialBoundary = partial; BufferUtil.clear(buffer); return false; @@ -642,78 +583,78 @@ public class MultiPartParser } content.limit(_partialBoundary); _partialBoundary = 0; - - if(LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); - if (_handler.content(content,false)) + if (LOG.isDebugEnabled()) + LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this); + + if (_handler.content(content, false)) return true; } } - + // Contains - int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); if (delimiter >= 0) { ByteBuffer content = buffer.slice(); content.limit(delimiter - buffer.arrayOffset() - buffer.position()); - + buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength()); setState(State.DELIMITER); - - if(LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),true,this); - return _handler.content(content,true); + if (LOG.isDebugEnabled()) + LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), true, this); + + return _handler.content(content, true); } - + // Ends With - _partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); if (_partialBoundary > 0) { ByteBuffer content = buffer.slice(); content.limit(content.limit() - _partialBoundary); - - if(LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); + + if (LOG.isDebugEnabled()) + LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this); BufferUtil.clear(buffer); - return _handler.content(content,false); + return _handler.content(content, false); } - + // There is normal content with no delimiter ByteBuffer content = buffer.slice(); - if(LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this); + if (LOG.isDebugEnabled()) + LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this); BufferUtil.clear(buffer); - return _handler.content(content,false); + return _handler.content(content, false); } - + /* ------------------------------------------------------------------------------- */ private void setState(State state) { if (DEBUG) - LOG.debug("{} --> {}",_state,state); + LOG.debug("{} --> {}", _state, state); _state = state; } - + /* ------------------------------------------------------------------------------- */ private void setState(FieldState state) { if (DEBUG) - LOG.debug("{}:{} --> {}",_state,_fieldState,state); + LOG.debug("{}:{} --> {}", _state, _fieldState, state); _fieldState = state; } - + /* ------------------------------------------------------------------------------- */ @Override public String toString() { - return String.format("%s{s=%s}",getClass().getSimpleName(),_state); + return String.format("%s{s=%s}", getClass().getSimpleName(), _state); } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -724,43 +665,45 @@ public class MultiPartParser */ public interface Handler { - public default void startPart() + default void startPart() { } - - public default void parsedField(String name, String value) + + @SuppressWarnings("unused") + default void parsedField(String name, String value) { } - - public default boolean headerComplete() + + default boolean headerComplete() { return false; } - - public default boolean content(ByteBuffer item, boolean last) + + @SuppressWarnings("unused") + default boolean content(ByteBuffer item, boolean last) { return false; } - - public default boolean messageComplete() + + default boolean messageComplete() { return false; } - - public default void earlyEOF() + + default void earlyEOF() { } } - + /* ------------------------------------------------------------------------------- */ @SuppressWarnings("serial") private static class IllegalCharacterException extends IllegalArgumentException { private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) { - super(String.format("Illegal character 0x%X",ch)); + super(String.format("Illegal character 0x%X", ch)); // 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))); } } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java index c12109383a7..4689b743407 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java @@ -24,7 +24,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -41,148 +40,142 @@ import java.util.concurrent.TimeUnit; import javax.servlet.MultipartConfigElement; import javax.servlet.ReadListener; -import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.Part; import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.IO; -import org.hamcrest.Matchers; import org.junit.Test; /** * MultiPartInputStreamTest - * - * */ public class MultiPartFormInputStreamTest { private static final String FILENAME = "stuff.txt"; protected String _contentType = "multipart/form-data, boundary=AaB03x"; protected String _multi = createMultipartRequestString(FILENAME); - protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + protected String _dirname = System.getProperty("java.io.tmpdir") + File.separator + "myfiles-" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); protected File _tmpDir = new File(_dirname); - public MultiPartFormInputStreamTest () + public MultiPartFormInputStreamTest() { _tmpDir.deleteOnExit(); } @Test public void testBadMultiPartRequest() - throws Exception + throws Exception { String boundary = "X0Y0"; - String str = "--" + boundary + "\r\n"+ - "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+ - "Content-Type: application/octet-stream\r\n\r\n"+ - "How now brown cow."+ - "\r\n--" + boundary + "-\r\n" + String str = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n" + + "Content-Type: application/octet-stream\r\n\r\n" + + "How now brown cow." + + "\r\n--" + boundary + "-\r\n" + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n" + "\r\n"; - + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - "multipart/form-data, boundary="+boundary, - config, - _tmpDir); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), + "multipart/form-data, boundary=" + boundary, + config, + _tmpDir); mpis.setDeleteOnExit(true); try { mpis.getParts(); - fail ("Incomplete Multipart"); + fail("Incomplete Multipart"); } catch (IOException e) { assertTrue(e.getMessage().startsWith("Incomplete")); } } - - + + @Test public void testFinalBoundaryOnly() - throws Exception + throws Exception { String delimiter = "\r\n"; final String boundary = "MockMultiPartTestBoundary"; - - + + // Malformed multipart request body containing only an arbitrary string of text, followed by the final boundary marker, delimited by empty lines. String str = delimiter + - "Hello world" + - delimiter + // Two delimiter markers, which make an empty line. - delimiter + - "--" + boundary + "--" + delimiter; - - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - "multipart/form-data, boundary="+boundary, - config, - _tmpDir); - mpis.setDeleteOnExit(true); - Collection parts = mpis.getParts(); - assertTrue(mpis.getParts().isEmpty()); - } - - - - @Test - public void testEmpty() - throws Exception - { - String delimiter = "\r\n"; - final String boundary = "MockMultiPartTestBoundary"; - - String str = - delimiter + - "--" + boundary + "--" + delimiter; - - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - "multipart/form-data, boundary="+boundary, - config, - _tmpDir); - mpis.setDeleteOnExit(true); - assertTrue(mpis.getParts().isEmpty()); - } - - @Test - public void testNoBoundaryRequest() - throws Exception - { - String str = "--\r\n"+ - "Content-Disposition: form-data; name=\"fileName\"\r\n"+ - "Content-Type: text/plain; charset=US-ASCII\r\n"+ - "Content-Transfer-Encoding: 8bit\r\n"+ - "\r\n"+ - "abc\r\n"+ - "--\r\n"+ - "Content-Disposition: form-data; name=\"desc\"\r\n"+ - "Content-Type: text/plain; charset=US-ASCII\r\n"+ - "Content-Transfer-Encoding: 8bit\r\n"+ - "\r\n"+ - "123\r\n"+ - "--\r\n"+ - "Content-Disposition: form-data; name=\"title\"\r\n"+ - "Content-Type: text/plain; charset=US-ASCII\r\n"+ - "Content-Transfer-Encoding: 8bit\r\n"+ - "\r\n"+ - "ttt\r\n"+ - "--\r\n"+ - "Content-Disposition: form-data; name=\"datafile5239138112980980385.txt\"; filename=\"datafile5239138112980980385.txt\"\r\n"+ - "Content-Type: application/octet-stream; charset=ISO-8859-1\r\n"+ - "Content-Transfer-Encoding: binary\r\n"+ - "\r\n"+ - "000\r\n"+ - "----\r\n"; + "Hello world" + + delimiter + // Two delimiter markers, which make an empty line. + delimiter + + "--" + boundary + "--" + delimiter; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - "multipart/form-data", - config, - _tmpDir); + "multipart/form-data, boundary=" + boundary, + config, + _tmpDir); + mpis.setDeleteOnExit(true); + assertTrue(mpis.getParts().isEmpty()); + } + + + @Test + public void testEmpty() + throws Exception + { + String delimiter = "\r\n"; + final String boundary = "MockMultiPartTestBoundary"; + + String str = + delimiter + + "--" + boundary + "--" + delimiter; + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), + "multipart/form-data, boundary=" + boundary, + config, + _tmpDir); + mpis.setDeleteOnExit(true); + assertTrue(mpis.getParts().isEmpty()); + } + + @Test + public void testNoBoundaryRequest() + throws Exception + { + String str = "--\r\n" + + "Content-Disposition: form-data; name=\"fileName\"\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "abc\r\n" + + "--\r\n" + + "Content-Disposition: form-data; name=\"desc\"\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "123\r\n" + + "--\r\n" + + "Content-Disposition: form-data; name=\"title\"\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "ttt\r\n" + + "--\r\n" + + "Content-Disposition: form-data; name=\"datafile5239138112980980385.txt\"; filename=\"datafile5239138112980980385.txt\"\r\n" + + "Content-Type: application/octet-stream; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "000\r\n" + + "----\r\n"; + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), + "multipart/form-data", + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(4)); @@ -193,7 +186,7 @@ public class MultiPartFormInputStreamTest assertThat(fileName.getSize(), is(3L)); IO.copy(fileName.getInputStream(), baos); assertThat(baos.toString("US-ASCII"), is("abc")); - + baos = new ByteArrayOutputStream(); Part desc = mpis.getPart("desc"); assertThat(desc, notNullValue()); @@ -206,101 +199,32 @@ public class MultiPartFormInputStreamTest 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")); } - + @Test public void testNonMultiPartRequest() - throws Exception + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - "Content-type: text/plain", - config, - _tmpDir); + "Content-type: text/plain", + config, + _tmpDir); mpis.setDeleteOnExit(true); assertTrue(mpis.getParts().isEmpty()); } - + @Test public void testNoBody() - throws Exception - { - String body = ""; - - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), - _contentType, - config, - _tmpDir); - mpis.setDeleteOnExit(true); - try - { - mpis.getParts(); - fail ("Missing initial multi part boundary"); - } - catch (IOException e) - { - assertTrue(e.getMessage().contains("Missing initial multi part boundary")); - } - } - - - @Test - public void testBodyAlreadyConsumed() - throws Exception { - ServletInputStream is = new ServletInputStream() { - - @Override - public boolean isFinished() - { - return true; - } - - @Override - public boolean isReady() - { - return false; - } - - @Override - public void setReadListener(ReadListener readListener) - { - } - - @Override - public int read() throws IOException - { - return 0; - } - - }; + String body = ""; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(is, - _contentType, - config, - _tmpDir); - mpis.setDeleteOnExit(true); - Collection parts = mpis.getParts(); - assertEquals(0, parts.size()); - } - - - - @Test - public void testWhitespaceBodyWithCRLF() - throws Exception - { - String whitespace = " \n\n\n\r\n\r\n\r\n\r\n"; - - - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), - _contentType, - config, - _tmpDir); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); try { @@ -312,55 +236,120 @@ public class MultiPartFormInputStreamTest assertTrue(e.getMessage().contains("Missing initial multi part boundary")); } } - + + @Test - public void testWhitespaceBody() + public void testBodyAlreadyConsumed() throws Exception + { + ServletInputStream is = new ServletInputStream() + { + + @Override + public boolean isFinished() { - String whitespace = " "; - + return true; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() + { + return 0; + } + + }; + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), - _contentType, - config, - _tmpDir); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(is, + _contentType, + config, + _tmpDir); + mpis.setDeleteOnExit(true); + Collection parts = mpis.getParts(); + assertEquals(0, parts.size()); + } + + + @Test + public void testWhitespaceBodyWithCRLF() + { + String whitespace = " \n\n\n\r\n\r\n\r\n\r\n"; + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); try { mpis.getParts(); - fail ("Multipart missing body"); + fail("Missing initial multi part boundary"); + } + catch (IOException e) + { + assertTrue(e.getMessage().contains("Missing initial multi part boundary")); + } + } + + @Test + public void testWhitespaceBody() + { + String whitespace = " "; + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), + _contentType, + config, + _tmpDir); + mpis.setDeleteOnExit(true); + try + { + mpis.getParts(); + fail("Multipart missing body"); } catch (IOException e) { assertTrue(e.getMessage().startsWith("Missing initial")); } } - + @Test public void testLeadingWhitespaceBodyWithCRLF() - throws Exception + throws Exception { - String body = " \n\n\n\r\n\r\n\r\n\r\n"+ - "--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" + + String body = " \n\n\n\r\n\r\n\r\n\r\n" + + "--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"; - - + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - - Collection parts = mpis.getParts(); + + Collection parts = mpis.getParts(); assertThat(parts, notNullValue()); assertThat(parts.size(), is(2)); Part field1 = mpis.getPart("field1"); @@ -376,71 +365,65 @@ public class MultiPartFormInputStreamTest assertTrue(baos.toString("US-ASCII").contains("aaaa")); } - - + @Test public void testLeadingWhitespaceBodyWithoutCRLF() throws Exception - { - String body = " "+ - "--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" + + { + String body = " " + + "--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"; - + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - - Collection parts = mpis.getParts(); + + Collection parts = mpis.getParts(); assertThat(parts, notNullValue()); assertThat(parts.size(), is(1)); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); Part stuff = mpis.getPart("stuff"); assertThat(stuff, notNullValue()); - baos = new ByteArrayOutputStream(); IO.copy(stuff.getInputStream(), baos); assertTrue(baos.toString("US-ASCII").contains("bbbbb")); } - - - - @Test public void testNoLimits() - throws Exception + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertFalse(parts.isEmpty()); } - + @Test - public void testRequestTooBig () - throws Exception + public void testRequestTooBig() + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); try @@ -456,21 +439,20 @@ public class MultiPartFormInputStreamTest @Test - public void testRequestTooBigThrowsErrorOnGetParts () - throws Exception + public void testRequestTooBigThrowsErrorOnGetParts() + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - Collection parts = null; //cause parsing try { - parts = mpis.getParts(); + mpis.getParts(); fail("Request should have exceeded maxRequestSize"); } catch (IllegalStateException e) @@ -481,7 +463,7 @@ public class MultiPartFormInputStreamTest //try again try { - parts = mpis.getParts(); + mpis.getParts(); fail("Request should have exceeded maxRequestSize"); } catch (IllegalStateException e) @@ -489,21 +471,20 @@ public class MultiPartFormInputStreamTest assertTrue(e.getMessage().startsWith("Request exceeds maxRequestSize")); } } - + @Test public void testFileTooBig() - throws Exception + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - Collection parts = null; try { - parts = mpis.getParts(); + mpis.getParts(); fail("stuff.txt should have been larger than maxFileSize"); } catch (IllegalStateException e) @@ -514,18 +495,17 @@ public class MultiPartFormInputStreamTest @Test public void testFileTooBigThrowsErrorOnGetParts() - throws Exception + throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - Collection parts = null; try { - parts = mpis.getParts(); //caused parsing + mpis.getParts(); //caused parsing fail("stuff.txt should have been larger than maxFileSize"); } catch (Throwable e) @@ -536,7 +516,7 @@ public class MultiPartFormInputStreamTest //test again after the parsing try { - parts = mpis.getParts(); //caused parsing + mpis.getParts(); //caused parsing fail("stuff.txt should have been larger than maxFileSize"); } catch (IllegalStateException e) @@ -546,23 +526,22 @@ public class MultiPartFormInputStreamTest } - @Test - public void testPartFileNotDeleted () throws Exception + public void testPartFileNotDeleted() throws Exception { - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), _contentType, config, _tmpDir); mpis.setDeleteOnExit(true); - Collection parts = mpis.getParts(); + mpis.getParts(); MultiPart part = (MultiPart)mpis.getPart("stuff"); - File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile(); - assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file + File stuff = part.getFile(); + assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file part.write("tptfd.txt"); - File tptfd = new File (_dirname+File.separator+"tptfd.txt"); + File tptfd = new File(_dirname + File.separator + "tptfd.txt"); assertThat(tptfd.exists(), is(true)); assertThat(stuff.exists(), is(false)); //got renamed part.cleanUp(); @@ -571,127 +550,125 @@ public class MultiPartFormInputStreamTest } @Test - public void testPartTmpFileDeletion () throws Exception + public void testPartTmpFileDeletion() throws Exception { - MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - Collection parts = mpis.getParts(); - + mpis.getParts(); + MultiPart part = (MultiPart)mpis.getPart("stuff"); - File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile(); - assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file - assertThat (stuff.exists(), is(true)); + File stuff = part.getFile(); + assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file + assertThat(stuff.exists(), is(true)); part.cleanUp(); assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup } @Test public void testLFOnlyRequest() - throws Exception + throws Exception { - String str = "--AaB03x\n"+ - "content-disposition: form-data; name=\"field1\"\n"+ - "\n"+ - "Joe Blow"+ - "\r\n--AaB03x\n"+ - "content-disposition: form-data; name=\"field2\"\n"+ - "\n"+ - "Other"+ + String str = "--AaB03x\n" + + "content-disposition: form-data; name=\"field1\"\n" + + "\n" + + "Joe Blow" + + "\r\n--AaB03x\n" + + "content-disposition: form-data; name=\"field2\"\n" + + "\n" + + "Other" + "\r\n--AaB03x--\n"; - + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(2)); Part p1 = mpis.getPart("field1"); - assertThat(p1, notNullValue()); + 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")); - - + + } @Test public void testCROnlyRequest() - throws Exception { - String str = "--AaB03x\r"+ - "content-disposition: form-data; name=\"field1\"\r"+ - "\r"+ - "Joe Blow\r"+ - "--AaB03x\r"+ - "content-disposition: form-data; name=\"field2\"\r"+ - "\r"+ - "Other\r"+ - "--AaB03x--\r"; - + String str = "--AaB03x\r" + + "content-disposition: form-data; name=\"field1\"\r" + + "\r" + + "Joe Blow\r" + + "--AaB03x\r" + + "content-disposition: form-data; name=\"field2\"\r" + + "\r" + + "Other\r" + + "--AaB03x--\r"; + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); try { Collection 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) + catch (Throwable e) { assertTrue(e.getMessage().contains("Bad EOL")); } } - + @Test public void testCRandLFMixRequest() - throws Exception { - String str = "--AaB03x\r"+ - "content-disposition: form-data; name=\"field1\"\r"+ - "\r"+ - "\nJoe Blow\n"+ - "\r"+ - "--AaB03x\r"+ - "content-disposition: form-data; name=\"field2\"\r"+ - "\r"+ - "Other\r"+ + String str = "--AaB03x\r" + + "content-disposition: form-data; name=\"field1\"\r" + + "\r" + + "\nJoe Blow\n" + + "\r" + + "--AaB03x\r" + + "content-disposition: form-data; name=\"field2\"\r" + + "\r" + + "Other\r" + "--AaB03x--\r"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); @@ -699,9 +676,9 @@ public class MultiPartFormInputStreamTest { Collection parts = mpis.getParts(); assertThat(parts.size(), is(2)); - + Part p1 = mpis.getPart("field1"); - assertThat(p1, notNullValue()); + assertThat(p1, notNullValue()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IO.copy(p1.getInputStream(), baos); assertThat(baos.toString("UTF-8"), is("\nJoe Blow\n")); @@ -712,56 +689,56 @@ public class MultiPartFormInputStreamTest IO.copy(p2.getInputStream(), baos); assertThat(baos.toString("UTF-8"), is("Other")); } - catch(Throwable e) + catch (Throwable e) { assertTrue(e.getMessage().contains("Bad EOL")); } } @Test - public void testBufferOverflowNoCRLF () throws Exception + public void testBufferOverflowNoCRLF() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write("--AaB03x\r\n".getBytes()); - for (int i=0; i< 3000; i++) //create content that will overrun default buffer size of BufferedInputStream + for (int i = 0; i < 3000; i++) //create content that will overrun default buffer size of BufferedInputStream { baos.write('a'); } MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(baos.toByteArray()), - _contentType, - config, - _tmpDir); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(baos.toByteArray()), + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); try { mpis.getParts(); - fail ("Header Line Exceeded Max Length"); + fail("Header Line Exceeded Max Length"); } catch (Throwable e) { assertTrue(e.getMessage().startsWith("Header Line Exceeded Max Length")); } - + } @Test - public void testCharsetEncoding () throws Exception + public void testCharsetEncoding() throws Exception { String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1"; - String str = "--TheBoundary\r\n"+ - "content-disposition: form-data; name=\"field1\"\r\n"+ - "\r\n"+ - "\nJoe Blow\n"+ - "\r\n"+ + String str = "--TheBoundary\r\n" + + "content-disposition: form-data; name=\"field1\"\r\n" + + "\r\n" + + "\nJoe Blow\n" + + "\r\n" + "--TheBoundary--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), - contentType, - config, - _tmpDir); + contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); @@ -772,107 +749,101 @@ public class MultiPartFormInputStreamTest public void testBadlyEncodedFilename() throws Exception { - String contents = "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"" +"Taken on Aug 22 \\ 2012.jpg" + "\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+"stuff"+ - "aaa"+"\r\n" + - "--AaB03x--\r\n"; + String contents = "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"" + "Taken on Aug 22 \\ 2012.jpg" + "\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + "stuff" + + "aaa" + "\r\n" + + "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg")); + assertThat(parts.iterator().next().getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg")); } @Test public void testBadlyEncodedMSFilename() throws Exception { - String contents = "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+"stuff"+ - "aaa"+"\r\n" + - "--AaB03x--\r\n"; + String contents = "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + "stuff" + + "aaa" + "\r\n" + + "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); + assertThat(parts.iterator().next().getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); } - + @Test public void testCorrectlyEncodedMSFilename() throws Exception { - String contents = "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+"stuff"+ - "aaa"+"\r\n" + - "--AaB03x--\r\n"; + String contents = "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + "stuff" + + "aaa" + "\r\n" + + "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); + assertThat(parts.iterator().next().getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); } - public void testMulti () - throws Exception - { - testMulti(FILENAME); - } - @Test public void testMultiWithSpaceInFilename() throws Exception { testMulti("stuff with spaces.txt"); } - + @Test - public void testWriteFilesIfContentDispositionFilename () - throws Exception + public void testWriteFilesIfContentDispositionFilename() + throws Exception { - String s = "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n"+ - "\r\n"+ - "Joe Blow\r\n"+ - "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+"sss"+ - "aaa"+"\r\n" + + String s = "--AaB03x\r\n" + + "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" + + "\r\n" + + "Joe Blow\r\n" + + "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + "sss" + + "aaa" + "\r\n" + "--AaB03x--\r\n"; //all default values for multipartconfig, ie file size threshold 0 MultipartConfigElement config = new MultipartConfigElement(_dirname); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(s.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); mpis.setWriteFilesWithFilenames(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(2)); Part field1 = mpis.getPart("field1"); //has a filename, should be written to a file File f = ((MultiPartFormInputStream.MultiPart)field1).getFile(); - assertThat(f,notNullValue()); // longer than 100 bytes, should already be a tmp file + assertThat(f, notNullValue()); // longer than 100 bytes, should already be a tmp file Part stuff = mpis.getPart("stuff"); f = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); //should only be in memory, no filename @@ -880,7 +851,7 @@ public class MultiPartFormInputStreamTest } - private void testMulti(String filename) throws IOException, ServletException, InterruptedException + private void testMulti(String filename) throws IOException { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()), @@ -891,126 +862,129 @@ public class MultiPartFormInputStreamTest Collection parts = mpis.getParts(); assertThat(parts.size(), is(2)); Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer - assertThat(field1,notNullValue()); - assertThat(field1.getName(),is("field1")); - InputStream is = field1.getInputStream(); + assertThat(field1, notNullValue()); + assertThat(field1.getName(), is("field1")); + ByteArrayOutputStream os = new ByteArrayOutputStream(); - IO.copy(is, os); + try (InputStream is = field1.getInputStream()) + { + IO.copy(is, os); + } assertEquals("Joe Blow", new String(os.toByteArray())); assertEquals(8, field1.getSize()); - + assertNotNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//in internal buffer field1.write("field1.txt"); assertNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//no longer in internal buffer - File f = new File (_dirname+File.separator+"field1.txt"); + File f = new File(_dirname + File.separator + "field1.txt"); assertTrue(f.exists()); field1.write("another_field1.txt"); //write after having already written - File f2 = new File(_dirname+File.separator+"another_field1.txt"); + File f2 = new File(_dirname + File.separator + "another_field1.txt"); assertTrue(f2.exists()); assertFalse(f.exists()); //should have been renamed field1.delete(); //file should be deleted assertFalse(f.exists()); //original file was renamed assertFalse(f2.exists()); //2nd written file was explicitly deleted - + MultiPart stuff = (MultiPart)mpis.getPart("stuff"); assertThat(stuff.getSubmittedFileName(), is(filename)); - assertThat(stuff.getContentType(),is("text/plain")); - assertThat(stuff.getHeader("Content-Type"),is("text/plain")); - assertThat(stuff.getHeaders("content-type").size(),is(1)); - assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\"")); - assertThat(stuff.getHeaderNames().size(),is(2)); - assertThat(stuff.getSize(),is(51L)); + assertThat(stuff.getContentType(), is("text/plain")); + assertThat(stuff.getHeader("Content-Type"), is("text/plain")); + assertThat(stuff.getHeaders("content-type").size(), is(1)); + assertThat(stuff.getHeader("content-disposition"), is("form-data; name=\"stuff\"; filename=\"" + filename + "\"")); + assertThat(stuff.getHeaderNames().size(), is(2)); + assertThat(stuff.getSize(), is(51L)); - File tmpfile = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); - 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"))); + File tmpfile = stuff.getFile(); + 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); - f = new File(_dirname+File.separator+filename); - assertThat(f.exists(),is(true)); + f = new File(_dirname + File.separator + filename); + assertThat(f.exists(), is(true)); assertThat(tmpfile.exists(), is(false)); try { - stuff.getInputStream(); + stuff.getInputStream(); } catch (Exception e) { - fail("Part.getInputStream() after file rename operation"); + fail("Part.getInputStream() after file rename operation: " + e.getMessage()); } f.deleteOnExit(); //clean up after test } - + @Test - public void testMultiSameNames () - throws Exception + public void testMultiSameNames() + throws Exception { - String sameNames = "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"stuff1.txt\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "00000\r\n"+ - "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "110000000000000000000000000000000000000000000000000\r\n"+ - "--AaB03x--\r\n"; - + String sameNames = "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"stuff1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "00000\r\n" + + "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "110000000000000000000000000000000000000000000000000\r\n" + + "--AaB03x--\r\n"; + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(sameNames.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertEquals(2, parts.size()); - for (Part p:parts) + for (Part p : parts) assertEquals("stuff", p.getName()); - + //if they all have the name name, then only retrieve the first one Part p = mpis.getPart("stuff"); assertNotNull(p); assertEquals(5, p.getSize()); } - + @Test - public void testBase64EncodedContent () throws Exception + public void testBase64EncodedContent() throws Exception { String contentWithEncodedPart = - "--AaB03x\r\n"+ - "Content-disposition: form-data; name=\"other\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "other" + "\r\n"+ - "--AaB03x\r\n"+ - "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+ - "Content-Transfer-Encoding: base64\r\n"+ - "Content-Type: application/octet-stream\r\n"+ - "\r\n"+ - B64Code.encode("hello jetty") + "\r\n"+ - "--AaB03x\r\n"+ - "Content-disposition: form-data; name=\"final\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "the end" + "\r\n"+ + "--AaB03x\r\n" + + "Content-disposition: form-data; name=\"other\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "other" + "\r\n" + + "--AaB03x\r\n" + + "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" + + "Content-Transfer-Encoding: base64\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n" + + B64Code.encode("hello jetty") + "\r\n" + + "--AaB03x\r\n" + + "Content-disposition: form-data; name=\"final\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "the end" + "\r\n" + "--AaB03x--\r\n"; - + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertEquals(3, parts.size()); - + Part p1 = mpis.getPart("other"); assertNotNull(p1); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IO.copy(p1.getInputStream(), baos); assertEquals("other", baos.toString("US-ASCII")); - + Part p2 = mpis.getPart("stuff"); assertNotNull(p2); baos = new ByteArrayOutputStream(); @@ -1025,70 +999,70 @@ public class MultiPartFormInputStreamTest } @Test - public void testQuotedPrintableEncoding () throws Exception + public void testQuotedPrintableEncoding() throws Exception { - String contentWithEncodedPart = - "--AaB03x\r\n"+ - "Content-disposition: form-data; name=\"other\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "other" + "\r\n"+ - "--AaB03x\r\n"+ - "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+ - "Content-Transfer-Encoding: quoted-printable\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "truth=3Dbeauty" + "\r\n"+ - "--AaB03x--\r\n"; + String contentWithEncodedPart = + "--AaB03x\r\n" + + "Content-disposition: form-data; name=\"other\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "other" + "\r\n" + + "--AaB03x\r\n" + + "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" + + "Content-Transfer-Encoding: quoted-printable\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "truth=3Dbeauty" + "\r\n" + + "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), - _contentType, - config, - _tmpDir); + _contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertEquals(2, parts.size()); - + Part p1 = mpis.getPart("other"); assertNotNull(p1); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IO.copy(p1.getInputStream(), baos); assertEquals("other", baos.toString("US-ASCII")); - + Part p2 = mpis.getPart("stuff"); assertNotNull(p2); baos = new ByteArrayOutputStream(); IO.copy(p2.getInputStream(), baos); assertEquals("truth=3Dbeauty", baos.toString("US-ASCII")); } - - + + @Test public void testGeneratedForm() - throws Exception + throws Exception { String contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"; - String body = "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "Content-Disposition: form-data; name=\"part1\"\r\n" + - "\n" + - "wNfミxVam﾿t\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + - "Content-Disposition: form-data; name=\"part2\"\r\n" + - "\r\n" + - "&ᄈᄎ￙ᅱᅢO\r\n" + + String body = "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + + "\r\n" + + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + + "Content-Disposition: form-data; name=\"part1\"\r\n" + + "\n" + + "wNfミxVam﾿t\r\n" + + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + + "Content-Disposition: form-data; name=\"part2\"\r\n" + + "\r\n" + + "&ᄈᄎ￙ᅱᅢO\r\n" + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--"; - - + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), - contentType, - config, - _tmpDir); + contentType, + config, + _tmpDir); mpis.setDeleteOnExit(true); - - Collection parts = mpis.getParts(); + + Collection parts = mpis.getParts(); assertThat(parts, notNullValue()); assertThat(parts.size(), is(2)); @@ -1097,15 +1071,15 @@ public class MultiPartFormInputStreamTest Part part2 = mpis.getPart("part2"); assertThat(part2, notNullValue()); } - - private String createMultipartRequestString(String filename) + + private static String createMultipartRequestString(String filename) { int length = filename.length(); String name = filename; if (length > 10) - name = filename.substring(0,10); - StringBuffer filler = new StringBuffer(); + name = filename.substring(0, 10); + StringBuilder filler = new StringBuilder(); int i = name.length(); while (i < 51) { @@ -1113,15 +1087,15 @@ public class MultiPartFormInputStreamTest i++; } - return "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n"+ - "\r\n"+ - "Joe Blow\r\n"+ - "--AaB03x\r\n"+ - "content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+name+ - filler.toString()+"\r\n" + - "--AaB03x--\r\n"; + return "--AaB03x\r\n" + + "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" + + "\r\n" + + "Joe Blow\r\n" + + "--AaB03x\r\n" + + "content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + name + + filler.toString() + "\r\n" + + "--AaB03x--\r\n"; } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartParserTest.java index 009be1af293..40dc519f14e 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartParserTest.java @@ -35,119 +35,127 @@ import org.junit.Test; public class MultiPartParserTest { - + @Test public void testEmptyPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); ByteBuffer data = BufferUtil.toBuffer(""); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); } - + @Test public void testNoPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY \r\n"); - data = BufferUtil.toBuffer("--BOUNDARY \r\n"); - parser.parse(data,false); + parser.parse(data, false); assertTrue(parser.isState(State.BODY_PART)); - assertThat(data.remaining(),is(0)); + assertThat(data.remaining(), is(0)); } @Test public void testPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); ByteBuffer data; data = BufferUtil.toBuffer("This is not part of a part\r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("More data that almost includes \n--BOUNDARY but no CR before."); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("Could be a boundary \r\n--BOUNDAR"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); 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)); + 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); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); } - + @Test public void testPreambleCompleteBoundary() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); - ByteBuffer data; + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); + ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n"); - data = BufferUtil.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.BODY_PART)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.BODY_PART)); + assertThat(data.remaining(), is(0)); } @Test public void testPreambleSplitBoundary() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); - ByteBuffer data; - - data = BufferUtil.toBuffer("This is not part of a part\r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); + ByteBuffer data = BufferUtil.toBuffer("This is not part of a part\r\n"); + + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("-"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("-"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("B"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.PREAMBLE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.PREAMBLE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("OUNDARY-"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.DELIMITER_CLOSE)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.DELIMITER_CLOSE)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("ignore\r"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.DELIMITER_PADDING)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.DELIMITER_PADDING)); + assertThat(data.remaining(), is(0)); data = BufferUtil.toBuffer("\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.BODY_PART)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.BODY_PART)); + assertThat(data.remaining(), is(0)); } - + @Test public void testFirstPartNoFields() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); + MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() + { + }, "BOUNDARY"); + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n"); - data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.FIRST_OCTETS)); - assertThat(data.remaining(),is(0)); + parser.parse(data, false); + assertThat(parser.getState(), is(State.FIRST_OCTETS)); + assertThat(data.remaining(), is(0)); } @Test @@ -162,149 +170,136 @@ public class MultiPartParserTest return true; } }; - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer 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); - assertThat(parser.getState(),is(State.FIRST_OCTETS)); - assertThat(data.remaining(),is(7)); - assertThat(handler.fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<>")); + + parser.parse(data, false); + assertThat(parser.getState(), is(State.FIRST_OCTETS)); + assertThat(data.remaining(), is(7)); + assertThat(handler.fields, Matchers.contains("name0: value0", "name1: value1", "name2: value 2", "<>")); } @Test public void testFirstPartNoContent() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\r\n" + "\r\n" + "\r\n" + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("<>")); - } + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("<>")); + } @Test public void testFirstPartNoContentNoCRLF() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\r\n" + "\r\n" + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("<>")); + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("<>")); } - + @Test public void testFirstPartContentLookingLikeNoCRLF() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\r\n" + "\r\n" + "-"); - parser.parse(data,false); + parser.parse(data, false); data = BufferUtil.toBuffer("Content!"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.OCTETS)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("-","Content!")); + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("-", "Content!")); } - + @Test public void testFirstPartPartialContent() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\n" + "\r\n" + "Hello\r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.OCTETS)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Hello")); - + parser.parse(data, false); + assertThat(parser.getState(), is(State.OCTETS)); + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Hello")); + data = BufferUtil.toBuffer( "Now is the time for all good ment to come to the aid of the party.\r\n" - + "How now brown cow.\r\n" - + "The quick brown fox jumped over the lazy dog.\r\n" - + "this is not a --BOUNDARY\r\n"); - parser.parse(data,false); - assertThat(parser.getState(),is(State.OCTETS)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Hello","\r\n","Now is the time for all good ment to come to the aid of the party.\r\n" + + "How now brown cow.\r\n" + + "The quick brown fox jumped over the lazy dog.\r\n" + + "this is not a --BOUNDARY\r\n"); + parser.parse(data, false); + assertThat(parser.getState(), is(State.OCTETS)); + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Hello", "\r\n", "Now is the time for all good ment to come to the aid of the party.\r\n" + "How now brown cow.\r\n" + "The quick brown fox jumped over the lazy dog.\r\n" + "this is not a --BOUNDARY")); } - + @Test public void testFirstPartShortContent() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\n" + "\r\n" + "Hello\r\n" + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Hello","<>")); + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Hello", "<>")); } - + @Test public void testFirstPartLongContent() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - ByteBuffer data = BufferUtil.toBuffer(""); - - data = BufferUtil.toBuffer("--BOUNDARY\r\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\r\n" + "name: value\n" + "\r\n" + "Now is the time for all good ment to come to the aid of the party.\r\n" @@ -312,25 +307,23 @@ public class MultiPartParserTest + "The quick brown fox jumped over the lazy dog.\r\n" + "\r\n" + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Now is the time for all good ment to come to the aid of the party.\r\n" + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Now is the time for all good ment to come to the aid of the party.\r\n" + "How now brown cow.\r\n" - + "The quick brown fox jumped over the lazy dog.\r\n","<>")); + + "The quick brown fox jumped over the lazy dog.\r\n", "<>")); } @Test public void testFirstPartLongContentNoCarriageReturn() { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); - - ByteBuffer data = BufferUtil.toBuffer(""); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); //boundary still requires carriage return - data = BufferUtil.toBuffer("--BOUNDARY\n" + ByteBuffer data = BufferUtil.toBuffer("--BOUNDARY\n" + "name: value\n" + "\n" + "Now is the time for all good men to come to the aid of the party.\n" @@ -338,15 +331,15 @@ public class MultiPartParserTest + "The quick brown fox jumped over the lazy dog.\n" + "\r\n" + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(0)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Now is the time for all good men to come to the aid of the party.\n" + assertThat(data.remaining(), is(0)); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Now is the time for all good men to come to the aid of the party.\n" + "How now brown cow.\n" - + "The quick brown fox jumped over the lazy dog.\n","<>")); + + "The quick brown fox jumped over the lazy dog.\n", "<>")); } - + @Test public void testBinaryPart() @@ -361,30 +354,31 @@ public class MultiPartParserTest @Override public boolean content(ByteBuffer buffer, boolean last) { - BufferUtil.append(bytes,buffer); + BufferUtil.append(bytes, buffer); return last; } }; - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n"; String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n"; - ByteBuffer data = BufferUtil.allocate(preamble.length()+random.length+epilogue.length()); - BufferUtil.append(data,BufferUtil.toBuffer(preamble)); - BufferUtil.append(data,ByteBuffer.wrap(random)); - BufferUtil.append(data,BufferUtil.toBuffer(epilogue)); + ByteBuffer data = BufferUtil.allocate(preamble.length() + random.length + epilogue.length()); + BufferUtil.append(data, BufferUtil.toBuffer(preamble)); + BufferUtil.append(data, ByteBuffer.wrap(random)); + BufferUtil.append(data, BufferUtil.toBuffer(epilogue)); - parser.parse(data,true); + parser.parse(data, true); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(data.remaining(),is(19)); - assertThat(bytes.array(),is(random)); + assertThat(data.remaining(), is(19)); + assertThat(bytes.array(), is(random)); } @Test - public void testEpilogue() { + public void testEpilogue() + { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); ByteBuffer data = BufferUtil.toBuffer("" + "--BOUNDARY\r\n" @@ -399,20 +393,21 @@ public class MultiPartParserTest + "--BOUNDARY"); - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(handler.fields,Matchers.contains("name: value", "<>")); - assertThat(handler.content,Matchers.contains("Hello","<>")); + assertThat(handler.fields, Matchers.contains("name: value", "<>")); + assertThat(handler.content, Matchers.contains("Hello", "<>")); - parser.parse(data,true); + parser.parse(data, true); assertThat(parser.getState(), is(State.END)); } @Test - public void testMultipleContent() { + public void testMultipleContent() + { TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY"); + MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); ByteBuffer data = BufferUtil.toBuffer("" + "--BOUNDARY\r\n" @@ -430,211 +425,213 @@ public class MultiPartParserTest + "epilogue here"); /* Test First Content Section */ - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); assertThat(handler.fields, Matchers.contains("name: value", "<>")); - assertThat(handler.content, Matchers.contains("Hello","<>")); + assertThat(handler.content, Matchers.contains("Hello", "<>")); /* Test Second Content Section */ - parser.parse(data,false); + parser.parse(data, false); assertThat(parser.getState(), is(State.DELIMITER)); - assertThat(handler.fields, Matchers.contains("name: value", "<>","powerLevel: 9001","<>")); - assertThat(handler.content, Matchers.contains("Hello","<>","secondary\r\ncontent","<>")); + assertThat(handler.fields, Matchers.contains("name: value", "<>", "powerLevel: 9001", "<>")); + assertThat(handler.content, Matchers.contains("Hello", "<>", "secondary\r\ncontent", "<>")); /* Test Progression to END State */ - parser.parse(data,true); + parser.parse(data, true); assertThat(parser.getState(), is(State.END)); - assertThat(data.remaining(),is(0)); + assertThat(data.remaining(), is(0)); } - @Test - public void testCrAsLineTermination() { - 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"+ - "Joe Blow\r\n"+ - "--AaB03x--\r\n"); - - - try - { - parser.parse(data,true); - fail("Invalid End of Line"); - } - catch(BadMessageException e) { - assertTrue(e.getMessage().contains("Bad EOL")); - } - - - } - - - - @Test - public void splitTest() + public void testCrAsLineTermination() { - TestHandler handler = new TestHandler() + TestHandler handler = new TestHandler() { @Override public boolean messageComplete() { return true; } - + @Override public boolean content(ByteBuffer buffer, boolean last) { - super.content(buffer,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" + + "Joe Blow\r\n" + + "--AaB03x--\r\n"); + + try + { + parser.parse(data, true); + fail("Invalid End of Line"); + } + catch (BadMessageException e) + { + assertTrue(e.getMessage().contains("Bad EOL")); + } + } + + + @Test + public void splitTest() + { + 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,"---------------------------9051914041544843365972754266"); - ByteBuffer data = BufferUtil.toBuffer(""+ - "POST / HTTP/1.1\n" + - "Host: localhost:8000\n" + - "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" + - "Accept-Language: en-US,en;q=0.5\n" + - "Accept-Encoding: gzip, deflate\n" + - "Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" + - "Connection: keep-alive\n" + - "Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" + - "Content-Length: 554\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Content-Disposition: form-data; name=\"text\"\n" + - "\n" + - "text default\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" + - "Content-Type: text/plain\n" + - "\n" + - "Content of a.txt.\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" + - "Content-Type: text/html\n" + - "\n" + - "Content of a.html.\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Field1: value1\n" + - "Field2: value2\n" + - "Field3: value3\n" + - "Field4: value4\n" + - "Field5: value5\n" + + MultiPartParser parser = new MultiPartParser(handler, "---------------------------9051914041544843365972754266"); + ByteBuffer data = BufferUtil.toBuffer("" + + "POST / HTTP/1.1\n" + + "Host: localhost:8000\n" + + "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\n" + + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" + + "Accept-Language: en-US,en;q=0.5\n" + + "Accept-Encoding: gzip, deflate\n" + + "Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" + + "Connection: keep-alive\n" + + "Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" + + "Content-Length: 554\n" + + "\r\n" + + "-----------------------------9051914041544843365972754266\n" + + "Content-Disposition: form-data; name=\"text\"\n" + + "\n" + + "text default\r\n" + + "-----------------------------9051914041544843365972754266\n" + + "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" + + "Content-Type: text/plain\n" + + "\n" + + "Content of a.txt.\n" + + "\r\n" + + "-----------------------------9051914041544843365972754266\n" + + "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" + + "Content-Type: text/html\n" + + "\n" + + "Content of a.html.\n" + + "\r\n" + + "-----------------------------9051914041544843365972754266\n" + + "Field1: value1\n" + + "Field2: value2\n" + + "Field3: value3\n" + + "Field4: value4\n" + + "Field5: value5\n" + "Field6: value6\n" + "Field7: value7\n" + "Field8: value8\n" + - "Field9: value\n" + + "Field9: value\n" + " 9\n" + "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Field1: value1\n" + - "\r\n"+ - "But the amount of denudation which the strata have\n" + - "in many places suffered, independently of the rate\n" + - "of accumulation of the degraded matter, probably\n" + - "offers the best evidence of the lapse of time. I remember\n" + - "having been much struck with the evidence of\n" + - "denudation, when viewing volcanic islands, which\n" + - "have been worn by the waves and pared all round\n" + - "into perpendicular cliffs of one or two thousand feet\n" + - "in height; for the gentle slope of the lava-streams,\n" + - "due to their formerly liquid state, showed at a glance\n" + - "how far the hard, rocky beds had once extended into\n" + + "-----------------------------9051914041544843365972754266\n" + + "Field1: value1\n" + + "\r\n" + + "But the amount of denudation which the strata have\n" + + "in many places suffered, independently of the rate\n" + + "of accumulation of the degraded matter, probably\n" + + "offers the best evidence of the lapse of time. I remember\n" + + "having been much struck with the evidence of\n" + + "denudation, when viewing volcanic islands, which\n" + + "have been worn by the waves and pared all round\n" + + "into perpendicular cliffs of one or two thousand feet\n" + + "in height; for the gentle slope of the lava-streams,\n" + + "due to their formerly liquid state, showed at a glance\n" + + "how far the hard, rocky beds had once extended into\n" + "the open ocean.\n" + "\r\n" + "-----------------------------9051914041544843365972754266--" + "===== ajlkfja;lkdj;lakjd;lkjf ==== epilogue here ==== kajflajdfl;kjafl;kjl;dkfja ====\n\r\n\r\r\r\n\n\n"); - - int length = data.remaining(); - for(int i = 0; i>" - , "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"" - , "Content-Type: text/plain","<>" - , "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"" - , "Content-Type: text/html","<>" - , "Field1: value1", "Field2: value2", "Field3: value3" - , "Field4: value4", "Field5: value5", "Field6: value6" - , "Field7: value7", "Field8: value8", "Field9: value 9", "<>" - , "Field1: value1","<>")); - - - assertThat(handler.contentString(), is(new String("text default"+"<>" - + "Content of a.txt.\n"+"<>" - + "Content of a.html.\n"+"<>" - + "<>" - + "But the amount of denudation which the strata have\n" + - "in many places suffered, independently of the rate\n" + - "of accumulation of the degraded matter, probably\n" + - "offers the best evidence of the lapse of time. I remember\n" + - "having been much struck with the evidence of\n" + - "denudation, when viewing volcanic islands, which\n" + - "have been worn by the waves and pared all round\n" + - "into perpendicular cliffs of one or two thousand feet\n" + - "in height; for the gentle slope of the lava-streams,\n" + - "due to their formerly liquid state, showed at a glance\n" + - "how far the hard, rocky beds had once extended into\n" + - "the open ocean.\n"+ "<>"))); - - handler.clear(); - parser.reset(); - } + + int length = data.remaining(); + for (int i = 0; i < length - 1; i++) + { + //partition 0 to i + ByteBuffer dataSeg = data.slice(); + dataSeg.position(0); + dataSeg.limit(i); + assertThat("First " + i, parser.parse(dataSeg, false), is(false)); + + //partition i + dataSeg = data.slice(); + dataSeg.position(i); + dataSeg.limit(i + 1); + assertThat("Second " + i, parser.parse(dataSeg, false), is(false)); + + //partition i to length + dataSeg = data.slice(); + dataSeg.position(i + 1); + dataSeg.limit(length); + assertThat("Third " + i, parser.parse(dataSeg, true), is(true)); + + assertThat(handler.fields, Matchers.contains("Content-Disposition: form-data; name=\"text\"", "<>" + , "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"" + , "Content-Type: text/plain", "<>" + , "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"" + , "Content-Type: text/html", "<>" + , "Field1: value1", "Field2: value2", "Field3: value3" + , "Field4: value4", "Field5: value5", "Field6: value6" + , "Field7: value7", "Field8: value8", "Field9: value 9", "<>" + , "Field1: value1", "<>")); + + + assertThat(handler.contentString(), is("text default" + "<>" + + "Content of a.txt.\n" + "<>" + + "Content of a.html.\n" + "<>" + + "<>" + + "But the amount of denudation which the strata have\n" + + "in many places suffered, independently of the rate\n" + + "of accumulation of the degraded matter, probably\n" + + "offers the best evidence of the lapse of time. I remember\n" + + "having been much struck with the evidence of\n" + + "denudation, when viewing volcanic islands, which\n" + + "have been worn by the waves and pared all round\n" + + "into perpendicular cliffs of one or two thousand feet\n" + + "in height; for the gentle slope of the lava-streams,\n" + + "due to their formerly liquid state, showed at a glance\n" + + "how far the hard, rocky beds had once extended into\n" + + "the open ocean.\n" + "<>")); + + handler.clear(); + parser.reset(); + } } - + @Test - public void testGeneratedForm() + public void testGeneratedForm() { - TestHandler handler = new TestHandler() + TestHandler handler = new TestHandler() { @Override public boolean messageComplete() { return true; } - + @Override public boolean content(ByteBuffer buffer, boolean last) { - super.content(buffer,last); + super.content(buffer, last); return false; } @@ -645,24 +642,24 @@ public class MultiPartParserTest } }; - MultiPartParser parser = new MultiPartParser(handler,"WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"); + MultiPartParser parser = new MultiPartParser(handler, "WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"); ByteBuffer data = BufferUtil.toBuffer("" - + "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "Content-Disposition: form-data; name=\"part1\"\r\n" + - "\n" + - "wNfミxVam﾿t\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + - "Content-Disposition: form-data; name=\"part2\"\r\n" + - "\r\n" + - "&ᄈᄎ￙ᅱᅢO\r\n" + + + "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + + "\r\n" + + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + + "Content-Disposition: form-data; name=\"part1\"\r\n" + + "\n" + + "wNfミxVam﾿t\r\n" + + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + + "Content-Disposition: form-data; name=\"part2\"\r\n" + + "\r\n" + + "&ᄈᄎ￙ᅱᅢO\r\n" + "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--"); - - parser.parse(data,true); + + parser.parse(data, true); assertThat(parser.getState(), is(State.END)); assertThat(handler.fields.size(), is(2)); - + } @@ -670,27 +667,28 @@ public class MultiPartParserTest { List fields = new ArrayList<>(); List content = new ArrayList<>(); - + @Override public void parsedField(String name, String value) { - fields.add(name+": "+value); + fields.add(name + ": " + value); } - + public String contentString() { StringBuilder sb = new StringBuilder(); - for(String s : content) sb.append(s); + for (String s : content) + sb.append(s); return sb.toString(); } - + @Override public boolean headerComplete() { fields.add("<>"); return false; } - + @Override public boolean content(ByteBuffer buffer, boolean last) { @@ -701,11 +699,10 @@ public class MultiPartParserTest return last; } - public void clear() { + public void clear() + { fields.clear(); content.clear(); } - } - } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java b/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java index 49924222a0c..7bf56f04e09 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/jmh/MultiPartBenchmark.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.http.jmh; import java.io.File; import java.io.InputStream; -import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -34,11 +33,6 @@ import javax.servlet.http.Part; import org.eclipse.jetty.http.MultiPartFormInputStream; import org.eclipse.jetty.http.MultiPartCaptureTest.MultipartExpectations; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.toolchain.test.TestingDir; -import org.eclipse.jetty.util.BufferUtil; -import org.junit.Rule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Level; @@ -48,14 +42,13 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.profile.CompilerProfiler; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Benchmark) -public class MultiPartBenchmark +public class MultiPartBenchmark { public static final int MAX_FILE_SIZE = Integer.MAX_VALUE; @@ -67,13 +60,14 @@ public class MultiPartBenchmark static File _file; static int _numSections; static int _numBytesPerSection; - + public static List data = new ArrayList<>(); + static { // Capture of raw request body contents from various browsers - + // simple form - 2 fields data.add("browser-capture-form1-android-chrome"); data.add("browser-capture-form1-android-firefox"); @@ -83,15 +77,15 @@ public class MultiPartBenchmark data.add("browser-capture-form1-ios-safari"); data.add("browser-capture-form1-msie"); data.add("browser-capture-form1-osx-safari"); - + // form submitted as shift-jis - data.add("browser-capture-sjis-form-edge"); + data.add("browser-capture-sjis-form-edge"); data.add("browser-capture-sjis-form-msie"); - + // form submitted as shift-jis (with HTML5 specific hidden _charset_ field) data.add("browser-capture-sjis-charset-form-edge"); data.add("browser-capture-sjis-charset-form-msie"); - + // form submitted with simple file upload data.add("browser-capture-form-fileupload-android-chrome"); data.add("browser-capture-form-fileupload-android-firefox"); @@ -101,7 +95,7 @@ public class MultiPartBenchmark data.add("browser-capture-form-fileupload-ios-safari"); data.add("browser-capture-form-fileupload-msie"); data.add("browser-capture-form-fileupload-safari"); - + // form submitted with 2 files (1 binary, 1 text) and 2 text fields data.add("browser-capture-form-fileupload-alt-chrome"); data.add("browser-capture-form-fileupload-alt-edge"); @@ -109,19 +103,19 @@ public class MultiPartBenchmark data.add("browser-capture-form-fileupload-alt-msie"); data.add("browser-capture-form-fileupload-alt-safari"); } - - @Param({"UTIL","HTTP"}) + + @Param({"UTIL", "HTTP"}) public static String parserType; - + @Setup(Level.Trial) public static void setupTrial() throws Exception { - _file = File.createTempFile("test01",null); + _file = File.createTempFile("test01", null); _file.deleteOnExit(); _numSections = 1; - _numBytesPerSection = 1024*1024*10; + _numBytesPerSection = 1024 * 1024 * 10; _contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"; String initialBoundary = "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n"; @@ -130,17 +124,18 @@ public class MultiPartBenchmark String headerStart = "Content-Disposition: form-data; name=\""; - for(int i=0; i<_numSections; i++) { + for (int i = 0; i < _numSections; i++) + { //boundary and headers - if(i==0) + if (i == 0) Files.write(_file.toPath(), initialBoundary.getBytes(), StandardOpenOption.APPEND); else Files.write(_file.toPath(), boundary.getBytes(), StandardOpenOption.APPEND); - + Files.write(_file.toPath(), headerStart.getBytes(), StandardOpenOption.APPEND); - Files.write(_file.toPath(), new String("part"+(i+1)).getBytes(), StandardOpenOption.APPEND); - Files.write(_file.toPath(), new String("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND); - + Files.write(_file.toPath(), ("part" + (i + 1)).getBytes(), StandardOpenOption.APPEND); + Files.write(_file.toPath(), ("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND); + //append random data byte[] data = new byte[_numBytesPerSection]; new Random().nextBytes(data); @@ -149,25 +144,9 @@ public class MultiPartBenchmark //closing boundary Files.write(_file.toPath(), closingBoundary.getBytes(), StandardOpenOption.APPEND); - - /* - // print out file to verify that it contains valid contents (just for testing) - InputStream in = Files.newInputStream(_file.toPath()); - System.out.println(); - while(in.available()>0) { - byte b[] = new byte[100]; - int read = in.read(b,0,100); - for(int i=0; i