From ca534d08fb312d662f8c01f241f030058aa75fb1 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 27 Mar 2018 09:11:57 +1100 Subject: [PATCH] Minor changes to code, documentation and formatting after review. Signed-off-by: Lachlan Roberts --- ...ser.java => MultiPartFormInputStream.java} | 477 +++++++++--------- .../eclipse/jetty/http/MultiPartParser.java | 438 ++++++++-------- ...java => MultiPartFormInputStreamTest.java} | 91 ++-- .../util/MultiPartInputStreamParser.java | 31 +- .../jetty/util/ReadLineInputStream.java | 1 + .../jetty/util/MultiPartInputStreamTest.java | 2 +- 6 files changed, 538 insertions(+), 502 deletions(-) rename jetty-http/src/main/java/org/eclipse/jetty/http/{MultiPartInputStreamParser.java => MultiPartFormInputStream.java} (59%) rename jetty-http/src/test/java/org/eclipse/jetty/http/{MultiPartInputStreamTest.java => MultiPartFormInputStreamTest.java} (88%) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartInputStreamParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java similarity index 59% rename from jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartInputStreamParser.java rename to jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java index 1cbde8dda3e..504781e0dd8 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartInputStreamParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java @@ -47,22 +47,21 @@ import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.QuotedStringTokenizer; -import org.eclipse.jetty.util.ReadLineInputStream; import org.eclipse.jetty.util.log.Log; 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 MultiPartInputStreamParser +public class MultiPartFormInputStream { - private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class); - private final int _bufferSize = 16*1024; - public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); + 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; @@ -74,8 +73,6 @@ public class MultiPartInputStreamParser protected boolean _deleteOnExit; protected boolean _writeFilesWithFilenames; - - public class MultiPart implements Part { protected String _name; @@ -88,8 +85,7 @@ public class MultiPartInputStreamParser protected long _size = 0; protected boolean _temporary = true; - public MultiPart (String name, String filename) - throws IOException + public MultiPart(String name, String filename) throws IOException { _name = name; _filename = filename; @@ -100,72 +96,69 @@ public class MultiPartInputStreamParser { return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,t=%b,f=%s}",_name,_filename,_contentType,_size,_temporary,_file); } - protected void setContentType (String contentType) + + protected void setContentType(String contentType) { _contentType = contentType; } - - protected void open() - throws IOException + protected void open() throws IOException { - //We will either be writing to a file, if it has a filename on the content-disposition - //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we - //will need to change to write to a file. + // We will either be writing to a file, if it has a filename on the content-disposition + // and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we + // will need to change to write to a file. if (isWriteFilesWithFilenames() && _filename != null && _filename.trim().length() > 0) { createFile(); } else { - //Write to a buffer in memory until we discover we've exceed the - //MultipartConfig fileSizeThreshold - _out = _bout= new ByteArrayOutputStream2(); + // Write to a buffer in memory until we discover we've exceed the + // MultipartConfig fileSizeThreshold + _out = _bout = new ByteArrayOutputStream2(); } } - protected void close() - throws IOException + protected void close() throws IOException { _out.close(); } - - protected void write (int b) - throws IOException + protected void write(int b) throws IOException { - if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize()) - throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); + if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize()) + throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); - if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null) + if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold() + && _file == null) createFile(); _out.write(b); - _size ++; + _size++; } - protected void write (byte[] bytes, int offset, int length) - throws IOException + protected void write(byte[] bytes, int offset, int length) throws IOException { - if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize()) - throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); + if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize()) + throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize"); - if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null) + 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 + protected void createFile() throws IOException { - /* Some statics just to make the code below easier to understand - * This get optimized away during the compile anyway */ + /* + * Some statics just to make the code below easier to understand This get optimized away during the compile anyway + */ final boolean USER = true; final boolean WORLD = false; - - _file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir); + + _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 @@ -176,7 +169,7 @@ public class MultiPartInputStreamParser if (_size > 0 && _out != null) { - //already written some bytes, so need to copy them into the file + // already written some bytes, so need to copy them into the file _out.flush(); _bout.writeTo(bos); _out.close(); @@ -185,8 +178,6 @@ public class MultiPartInputStreamParser _out = bos; } - - protected void setHeaders(MultiMap headers) { _headers = headers; @@ -206,10 +197,10 @@ public class MultiPartInputStreamParser */ @Override public String getHeader(String name) - { + { if (name == null) return null; - return _headers.getValue(name.toLowerCase(Locale.ENGLISH), 0); + return _headers.getValue(name.toLowerCase(Locale.ENGLISH),0); } /** @@ -227,7 +218,7 @@ public class MultiPartInputStreamParser @Override public Collection getHeaders(String name) { - return _headers.getValues(name); + return _headers.getValues(name); } /** @@ -236,19 +227,18 @@ public class MultiPartInputStreamParser @Override public InputStream getInputStream() throws IOException { - if (_file != null) - { - //written to a file, whether temporary or not - return new BufferedInputStream (new FileInputStream(_file)); - } - else - { - //part content is in memory - return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); - } + if (_file != null) + { + // written to a file, whether temporary or not + return new BufferedInputStream(new FileInputStream(_file)); + } + else + { + // part content is in memory + return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); + } } - /** * @see javax.servlet.http.Part#getSubmittedFileName() */ @@ -260,7 +250,7 @@ public class MultiPartInputStreamParser public byte[] getBytes() { - if (_bout!=null) + if (_bout != null) return _bout.toByteArray(); return null; } @@ -271,7 +261,7 @@ public class MultiPartInputStreamParser @Override public String getName() { - return _name; + return _name; } /** @@ -293,8 +283,8 @@ public class MultiPartInputStreamParser { _temporary = false; - //part data is only in the ByteArrayOutputStream and never been written to disk - _file = new File (_tmpDir, fileName); + // part data is only in the ByteArrayOutputStream and never been written to disk + _file = new File(_tmpDir,fileName); BufferedOutputStream bos = null; try @@ -312,19 +302,19 @@ public class MultiPartInputStreamParser } else { - //the part data is already written to a temporary file, just rename it + // 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) + * Remove the file, whether or not Part.write() was called on it (ie no longer temporary) + * * @see javax.servlet.http.Part#delete() */ @Override @@ -337,7 +327,8 @@ public class MultiPartInputStreamParser /** * 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 { @@ -345,47 +336,48 @@ public class MultiPartInputStreamParser _file.delete(); } - /** * Get the file + * * @return the file, if any, the data has been written to. */ - public File getFile () + public File getFile() { return _file; } - /** * Get the filename from the content-disposition. + * * @return null or the filename */ - public String getContentDispositionFilename () + public String getContentDispositionFilename() { 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 MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) + public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) { _contentType = contentType; _config = config; _contextTmpDir = contextTmpDir; if (_contextTmpDir == null) - _contextTmpDir = new File (System.getProperty("java.io.tmpdir")); + _contextTmpDir = new File(System.getProperty("java.io.tmpdir")); if (_config == null) _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); - + if (in instanceof ServletInputStream) { if (((ServletInputStream)in).isFinished()) @@ -394,11 +386,12 @@ public class MultiPartInputStreamParser return; } } - _in = new ReadLineInputStream(in); + _in = new BufferedInputStream(in); } /** * Get the already parsed parts. + * * @return the parts that were parsed */ public Collection getParsedParts() @@ -408,9 +401,9 @@ public class MultiPartInputStreamParser Collection> values = _parts.values(); List parts = new ArrayList<>(); - for (List o: values) + for (List o : values) { - List asList = LazyList.getList(o, false); + List asList = LazyList.getList(o,false); parts.addAll(asList); } return parts; @@ -419,20 +412,20 @@ public class MultiPartInputStreamParser /** * Delete any tmp storage for parts, and clear out the parts list. * - * @throws MultiException if unable to delete the parts + * @throws MultiException + * if unable to delete the parts */ - public void deleteParts () - throws MultiException + public void deleteParts() throws MultiException { Collection parts = getParsedParts(); MultiException err = new MultiException(); - for (Part p:parts) + for (Part p : parts) { try { - ((MultiPartInputStreamParser.MultiPart)p).cleanUp(); + ((MultiPart)p).cleanUp(); } - catch(Exception e) + catch (Exception e) { err.add(e); } @@ -442,53 +435,51 @@ public class MultiPartInputStreamParser err.ifExceptionThrowMulti(); } - /** * 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 + public Collection getParts() throws IOException { parse(); throwIfError(); - Collection> values = _parts.values(); List parts = new ArrayList<>(); - for (List o: values) + 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 + public Part getPart(String name) throws IOException { parse(); - throwIfError(); - return _parts.getValue(name, 0); + throwIfError(); + 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 + protected void throwIfError() throws IOException { if (_err != null) { @@ -505,34 +496,33 @@ public class MultiPartInputStreamParser * Parse, if necessary, the multipart stream. * */ - protected void parse () + protected void parse() { - //have we already parsed the input? + // have we already parsed the input? if (_parts != null || _err != null) return; try { - //initialize + // initialize _parts = new MultiMap<>(); - //if its not a multipart request, don't parse it + // 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 + // sort out the location to which to write the files if (_config.getLocation() == null) _tmpDir = _contextTmpDir; else if ("".equals(_config.getLocation())) _tmpDir = _contextTmpDir; else { - File f = new File (_config.getLocation()); + File f = new File(_config.getLocation()); if (f.isAbsolute()) _tmpDir = f; else - _tmpDir = new File (_contextTmpDir, _config.getLocation()); + _tmpDir = new File(_contextTmpDir,_config.getLocation()); } if (!_tmpDir.exists()) @@ -542,72 +532,74 @@ public class MultiPartInputStreamParser int bstart = _contentType.indexOf("boundary="); if (bstart >= 0) { - int bend = _contentType.indexOf(";", bstart); - bend = (bend < 0? _contentType.length(): bend); + 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); - + MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary); // Create a buffer to store data from stream // byte[] data = new byte[_bufferSize]; - int len=0; + int len = 0; - /* keep running total of size of bytes read from input - * and throw an exception if exceeds MultipartConfigElement._maxRequestSize */ - long total = 0; + /* + * keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize + */ + long total = 0; - while(true) + while (true) { len = _in.read(data); - if(len > 0) + if (len > 0) { - total+=len; - if(_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) + total += len; + if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) { - _err = new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")"); + _err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")"); return; } ByteBuffer buffer = BufferUtil.toBuffer(data); buffer.limit(len); - parser.parse(buffer, false); - } - else if (len == -1) + if (parser.parse(buffer,false)) + break; + + 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) + // check for exceptions + if (_err != null) { return; } - //check we read to the end of the message - if(parser.getState() != MultiPartParser.State.END) + // check we read to the end of the message + if (parser.getState() != MultiPartParser.State.END) { - if(parser.getState() == MultiPartParser.State.PREAMBLE) + if (parser.getState() == MultiPartParser.State.PREAMBLE) _err = new IOException("Missing initial multi part boundary"); else _err = new IOException("Incomplete Multipart"); } - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) { LOG.debug("Parsing Complete {} err={}",parser,_err); } - } catch (Throwable e) { @@ -616,98 +608,83 @@ public class MultiPartInputStreamParser } } - + class Handler implements MultiPartParser.Handler { - - private MultiPart _part=null; - private String contentDisposition=null; - private String contentType=null; + + private MultiPart _part = null; + private String contentDisposition = null; + private String contentType = null; private MultiMap headers = new MultiMap<>(); - + @Override - public boolean messageComplete() { return true; } - + public boolean messageComplete() + { + return true; + } + @Override public void parsedField(String key, String value) { // Add to headers and mark if one of these fields. // - headers.put(key.toLowerCase(Locale.ENGLISH), value); + headers.put(key.toLowerCase(Locale.ENGLISH),value); if (key.equalsIgnoreCase("content-disposition")) - contentDisposition=value; + contentDisposition = value; else if (key.equalsIgnoreCase("content-type")) - contentType = value; - + contentType = value; } @Override - public boolean headerComplete() { - - try { - + public boolean headerComplete() + { + try + { // Extract content-disposition - boolean form_data=false; - if(contentDisposition==null) + boolean form_data = false; + if (contentDisposition == null) { throw new IOException("Missing content-disposition"); } - - QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true); - String name=null; - String filename=null; - while(tok.hasMoreTokens()) + + QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true); + String name = null; + String filename = null; + while (tok.hasMoreTokens()) { - String t=tok.nextToken().trim(); - String tl=t.toLowerCase(Locale.ENGLISH); - if(t.startsWith("form-data")) - form_data=true; - else if(tl.startsWith("name=")) - name=value(t); - else if(tl.startsWith("filename=")) - filename=filenameValue(t); + String t = tok.nextToken().trim(); + String tl = t.toLowerCase(Locale.ENGLISH); + if (t.startsWith("form-data")) + form_data = true; + else if (tl.startsWith("name=")) + name = value(t); + else if (tl.startsWith("filename=")) + filename = filenameValue(t); } - + // Check disposition - if(!form_data) + if (!form_data) { return false; } - //It is valid for reset and submit buttons to have an empty name. - //If no name is supplied, the browser skips sending the info for that field. - //However, if you supply the empty string as the name, the browser sends the - //field, with name as the empty string. So, only continue this loop if we - //have not yet seen a name field. - if(name==null) + // It is valid for reset and submit buttons to have an empty name. + // If no name is supplied, the browser skips sending the info for that field. + // However, if you supply the empty string as the name, the browser sends the + // field, with name as the empty string. So, only continue this loop if we + // have not yet seen a name field. + if (name == null) { return false; } - //create the new part - _part = new MultiPart(name, filename); + // create the new part + _part = new MultiPart(name,filename); _part.setHeaders(headers); _part.setContentType(contentType); - _parts.add(name, _part); - } - catch (Exception e) - { - _err = e; - return true; - } - - return false; - } - - @Override - public boolean content(ByteBuffer buffer, boolean last) - { - if (BufferUtil.hasContent(buffer)) - { + _parts.add(name,_part); + try { - //write the content data to the part _part.open(); - _part.write(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining()); - _part.close(); } catch (IOException e) { @@ -715,13 +692,48 @@ public class MultiPartInputStreamParser return true; } } - - if (last) - reset(); - + catch (Exception e) + { + _err = e; + return true; + } + return false; } - + + @Override + public boolean content(ByteBuffer buffer, boolean last) + { + if (BufferUtil.hasContent(buffer)) + { + try + { + _part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + } + catch (IOException e) + { + _err = e; + return true; + } + } + + if (last) + { + try + { + _part.close(); + } + catch (IOException e) + { + _err = e; + return true; + } + reset(); + } + + return false; + } + public void reset() { _part = null; @@ -729,21 +741,27 @@ public class MultiPartInputStreamParser contentType = null; headers = new MultiMap<>(); } - + + @Override + public void earlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("Early EOF {}",MultiPartFormInputStream.this); + } + } - - + public void setDeleteOnExit(boolean deleteOnExit) { _deleteOnExit = deleteOnExit; } - public void setWriteFilesWithFilenames (boolean writeFilesWithFilenames) + public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames) { _writeFilesWithFilenames = writeFilesWithFilenames; } - - public boolean isWriteFilesWithFilenames () + + public boolean isWriteFilesWithFilenames() { return _writeFilesWithFilenames; } @@ -753,42 +771,39 @@ public class MultiPartInputStreamParser return _deleteOnExit; } - /* ------------------------------------------------------------ */ private String value(String nameEqualsValue) { int idx = nameEqualsValue.indexOf('='); - String value = nameEqualsValue.substring(idx+1).trim(); + String value = nameEqualsValue.substring(idx + 1).trim(); return QuotedStringTokenizer.unquoteOnly(value); } - /* ------------------------------------------------------------ */ private String filenameValue(String nameEqualsValue) { int idx = nameEqualsValue.indexOf('='); - String value = nameEqualsValue.substring(idx+1).trim(); + String value = nameEqualsValue.substring(idx + 1).trim(); if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) { - //incorrectly escaped IE filenames that have the whole path - //we just strip any leading & trailing quotes and leave it as is - char first=value.charAt(0); - if (first=='"' || first=='\'') - value=value.substring(1); - char last=value.charAt(value.length()-1); - if (last=='"' || last=='\'') - value = value.substring(0,value.length()-1); + // incorrectly escaped IE filenames that have the whole path + // we just strip any leading & trailing quotes and leave it as is + char first = value.charAt(0); + if (first == '"' || first == '\'') + value = value.substring(1); + char last = value.charAt(value.length() - 1); + if (last == '"' || last == '\'') + value = value.substring(0,value.length() - 1); return value; } else - //unquote the string, but allow any backslashes that don't - //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); + // unquote the string, but allow any backslashes that don't + // 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); } - } 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 b6a74c6b70b..7e6c40ec103 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 @@ -29,7 +29,6 @@ import org.eclipse.jetty.util.SearchPattern; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; - /* ------------------------------------------------------------ */ /* * RFC2046 and RFC7578 @@ -114,42 +113,42 @@ 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)';'; - + static final byte COLON = (byte)':'; + static final byte TAB = 0x09; + static final byte LINE_FEED = 0x0A; + static final byte CARRIAGE_RETURN = 0x0D; + static final byte SPACE = 0x20; + static final byte[] CRLF = + { CARRIAGE_RETURN, LINE_FEED }; + static final byte SEMI_COLON = (byte)';'; // States public enum FieldState { FIELD, IN_NAME, - AFTER_NAME, + AFTER_NAME, VALUE, IN_VALUE } - + // States public enum State { PREAMBLE, DELIMITER, - DELIMITER_PADDING, - DELIMITER_CLOSE, + DELIMITER_PADDING, + DELIMITER_CLOSE, BODY_PART, - FIRST_OCTETS, - OCTETS, - EPILOGUE, + FIRST_OCTETS, + OCTETS, + EPILOGUE, END } private final static EnumSet __delimiterStates = EnumSet.of(State.DELIMITER,State.DELIMITER_CLOSE,State.DELIMITER_PADDING); - - private final boolean DEBUG=LOG.isDebugEnabled(); + + private final boolean DEBUG = LOG.isDebugEnabled(); private final Handler _handler; private final SearchPattern _delimiterSearch; @@ -162,7 +161,7 @@ public class MultiPartParser private boolean _cr; private ByteBuffer _patternBuffer; - private final StringBuilder _string=new StringBuilder(); + private final StringBuilder _string = new StringBuilder(); private int _length; private int _totalHeaderLineLength = -1; @@ -173,7 +172,7 @@ public class MultiPartParser { _handler = handler; - String delimiter = "\r\n--"+boundary; + String delimiter = "\r\n--" + boundary; _patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII)); _delimiterSearch = SearchPattern.compile(_patternBuffer.array()); } @@ -184,14 +183,13 @@ public class MultiPartParser _fieldState = FieldState.FIELD; _partialBoundary = 2; // No CRLF if no preamble } - - + /* ------------------------------------------------------------------------------- */ public Handler getHandler() { return _handler; } - + /* ------------------------------------------------------------------------------- */ public State getState() { @@ -205,54 +203,57 @@ public class MultiPartParser } /* ------------------------------------------------------------------------------- */ - enum CharState { ILLEGAL, CR, LF, LEGAL } + 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 ) + // 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]; + __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[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; + __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); + __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); } @@ -261,7 +262,7 @@ public class MultiPartParser { return BufferUtil.hasContent(buffer); } - + /* ------------------------------------------------------------------------------- */ private byte getNextByte(ByteBuffer buffer) { @@ -269,17 +270,17 @@ public class MultiPartParser byte ch = buffer.get(); CharState s = __charState[0xff & ch]; - switch(s) + switch (s) { case LF: - _cr=false; + _cr = false; return ch; case CR: if (_cr) throw new BadMessageException("Bad EOL"); - - _cr=true; + + _cr = true; if (buffer.hasRemaining()) return getNextByte(buffer); @@ -299,75 +300,75 @@ public class MultiPartParser } } - /* ------------------------------------------------------------------------------- */ private void setString(String s) { _string.setLength(0); _string.append(s); - _length=s.length(); + _length = s.length(); } /* ------------------------------------------------------------------------------- */ private String takeString() { _string.setLength(_length); - String s =_string.toString(); + String s = _string.toString(); _string.setLength(0); - _length=-1; + _length = -1; return s; } - - + /* ------------------------------------------------------------------------------- */ /** * Parse until next Event. - * @param buffer the buffer to parse + * + * @param buffer + * the buffer to parse * @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 == false && BufferUtil.hasContent(buffer)) { - switch(_state) + 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 (last && BufferUtil.isEmpty(buffer)) { - if(_state == State.EPILOGUE) + if (_state == State.EPILOGUE) { _state = State.END; return _handler.messageComplete(); @@ -378,21 +379,21 @@ public class MultiPartParser return true; } } - + return handle; } - + /* ------------------------------------------------------------------------------- */ private void parsePreamble(ByteBuffer buffer) { - if (_partialBoundary>0) + if (_partialBoundary > 0) { - int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary); - if (partial>0) + int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary); + if (partial > 0) { - if (partial==_delimiterSearch.getLength()) + if (partial == _delimiterSearch.getLength()) { - buffer.position(buffer.position()+partial-_partialBoundary); + buffer.position(buffer.position() + partial - _partialBoundary); _partialBoundary = 0; setState(State.DELIMITER); return; @@ -402,61 +403,61 @@ public class MultiPartParser BufferUtil.clear(buffer); return; } - + _partialBoundary = 0; } - - int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining()); - if (delimiter>=0) + + int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + if (delimiter >= 0) { - buffer.position(delimiter-buffer.arrayOffset()+_delimiterSearch.getLength()); + 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) - { + { while (__delimiterStates.contains(_state) && hasNextByte(buffer)) { - byte b=getNextByte(buffer); - if (b==0) + byte b = getNextByte(buffer); + if (b == 0) return; - - if (b=='\n') + + if (b == '\n') { setState(State.BODY_PART); _handler.startPart(); return; - } + } - switch(_state) + switch (_state) { case DELIMITER: - if (b=='-') + if (b == '-') setState(State.DELIMITER_CLOSE); else setState(State.DELIMITER_PADDING); continue; - + case DELIMITER_CLOSE: - if (b=='-') + if (b == '-') { setState(State.EPILOGUE); return; } setState(State.DELIMITER_PADDING); continue; - + case DELIMITER_PADDING: default: - continue; + continue; } } } @@ -468,44 +469,43 @@ public class MultiPartParser protected boolean parseMimePartHeaders(ByteBuffer buffer) { // Process headers - while (_state==State.BODY_PART && hasNextByte(buffer)) + while (_state == State.BODY_PART && hasNextByte(buffer)) { // process each character - byte b=getNextByte(buffer); - if (b==0) + byte b = getNextByte(buffer); + if (b == 0) break; - - if(b!=LINE_FEED) - _totalHeaderLineLength++; - - if(_totalHeaderLineLength > _maxHeaderLineLength) - throw new IllegalStateException("Header Line Exceeded Max Length"); + if (b != LINE_FEED) + _totalHeaderLineLength++; + + if (_totalHeaderLineLength > _maxHeaderLineLength) + throw new IllegalStateException("Header Line Exceeded Max Length"); switch (_fieldState) { case FIELD: - switch(b) + switch (b) { case SPACE: case TAB: { // Folded field value! - - if (_fieldName==null) + + if (_fieldName == null) throw new IllegalStateException("First field folded"); - - if (_fieldValue==null) + + if (_fieldValue == null) { _string.setLength(0); - _length=0; + _length = 0; } else { setString(_fieldValue); _string.append(' '); _length++; - _fieldValue=null; + _fieldValue = null; } setState(FieldState.VALUE); break; @@ -530,100 +530,100 @@ public class MultiPartParser setState(FieldState.IN_NAME); _string.setLength(0); _string.append((char)b); - _length=1; + _length = 1; } } break; case IN_NAME: - switch(b) + switch (b) { case COLON: - _fieldName=takeString(); - _length=-1; + _fieldName = takeString(); + _length = -1; setState(FieldState.VALUE); break; - + case SPACE: - //Ignore trailing whitespaces + // Ignore trailing whitespaces setState(FieldState.AFTER_NAME); break; - + default: _string.append((char)b); - _length=_string.length(); + _length = _string.length(); break; } break; - + case AFTER_NAME: - switch(b) + switch (b) { case COLON: - _fieldName=takeString(); - _length=-1; + _fieldName = takeString(); + _length = -1; setState(FieldState.VALUE); break; - + case LINE_FEED: - _fieldName=takeString(); + _fieldName = takeString(); _string.setLength(0); - _fieldValue=""; - _length=-1; + _fieldValue = ""; + _length = -1; break; - + case SPACE: break; - + default: throw new IllegalCharacterException(_state,b,buffer); } break; - + case VALUE: - switch(b) + switch (b) { case LINE_FEED: _string.setLength(0); - _fieldValue=""; - _length=-1; + _fieldValue = ""; + _length = -1; setState(FieldState.FIELD); break; - + case SPACE: case TAB: break; - + default: - _string.append((char)(0xff&b)); - _length=_string.length(); + _string.append((char)(0xff & b)); + _length = _string.length(); setState(FieldState.IN_VALUE); break; } break; case IN_VALUE: - switch(b) + switch (b) { case SPACE: - _string.append((char)(0xff&b)); + _string.append((char)(0xff & b)); break; case LINE_FEED: if (_length > 0) { - _fieldValue=takeString(); - _length=-1; - _totalHeaderLineLength=-1; + _fieldValue = takeString(); + _length = -1; + _totalHeaderLineLength = -1; } setState(FieldState.FIELD); break; default: - _string.append((char)(0xff&b)); - if (b>SPACE || b<0) - _length=_string.length(); + _string.append((char)(0xff & b)); + if (b > SPACE || b < 0) + _length = _string.length(); break; } break; @@ -635,95 +635,91 @@ public class MultiPartParser } return false; } - + /* ------------------------------------------------------------------------------- */ private void handleField() { - if (_fieldName!=null && _fieldValue!=null) + if (_fieldName != null && _fieldValue != null) _handler.parsedField(_fieldName,_fieldValue); _fieldName = _fieldValue = null; } - + /* ------------------------------------------------------------------------------- */ protected boolean parseOctetContent(ByteBuffer buffer) { - - //Starts With - if (_partialBoundary>0) + + // Starts With + if (_partialBoundary > 0) { - int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining(),_partialBoundary); - if (partial>0) + int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary); + if (partial > 0) { - if (partial==_delimiterSearch.getLength()) + if (partial == _delimiterSearch.getLength()) { buffer.position(buffer.position() + _delimiterSearch.getLength() - _partialBoundary); setState(State.DELIMITER); _partialBoundary = 0; - return _handler.content(BufferUtil.EMPTY_BUFFER, true); + return _handler.content(BufferUtil.EMPTY_BUFFER,true); } _partialBoundary = partial; - BufferUtil.clear(buffer); + BufferUtil.clear(buffer); return false; } else { - //output up to _partialBoundary of the search pattern + // output up to _partialBoundary of the search pattern ByteBuffer content = _patternBuffer.slice(); - if (_state==State.FIRST_OCTETS) + if (_state == State.FIRST_OCTETS) { setState(State.OCTETS); content.position(2); } content.limit(_partialBoundary); _partialBoundary = 0; - - if (_handler.content(content, false)) + + if (_handler.content(content,false)) return true; } } - - + // Contains - int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.remaining()); - if (delimiter>=0) + 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); - - return _handler.content(content, true); + + return _handler.content(content,true); } - // Ends With - _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset()+buffer.position(), buffer.remaining()); - if(_partialBoundary > 0) + _partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining()); + if (_partialBoundary > 0) { ByteBuffer content = buffer.slice(); content.limit(content.limit() - _partialBoundary); - + BufferUtil.clear(buffer); - return _handler.content(content, false); + return _handler.content(content,false); } - - + // There is normal content with no delimiter ByteBuffer content = buffer.slice(); BufferUtil.clear(buffer); - return _handler.content(content, false); + return _handler.content(content,false); } - /* ------------------------------------------------------------------------------- */ private void setState(State state) { if (DEBUG) LOG.debug("{} --> {}",_state,state); - _state=state; + _state = state; } /* ------------------------------------------------------------------------------- */ @@ -731,47 +727,59 @@ public class MultiPartParser { if (DEBUG) LOG.debug("{}:{} --> {}",_state,_fieldState,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); } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ - /* Event Handler interface - * These methods return true if the caller should process the events - * so far received (eg return from parseNext and call HttpChannel.handle). - * If multiple callbacks are called in sequence (eg - * headerComplete then messageComplete) from the same point in the parsing - * then it is sufficient for the caller to process the events only once. + /* + * Event Handler interface These methods return true if the caller should process the events so far received (eg return from parseNext and call + * HttpChannel.handle). If multiple callbacks are called in sequence (eg headerComplete then messageComplete) from the same point in the parsing then it is + * sufficient for the caller to process the events only once. */ public interface Handler { - public default void startPart() {} - public default void parsedField(String name, String value) {} - public default boolean headerComplete() {return false;} - - public default boolean content(ByteBuffer item, boolean last) {return false;} - - public default boolean messageComplete() {return false;} + public default void startPart() + { + } - public default void earlyEOF() {} + public default void parsedField(String name, String value) + { + } + + public default boolean headerComplete() + { + return false; + } + + public default boolean content(ByteBuffer item, boolean last) + { + return false; + } + + public default boolean messageComplete() + { + return false; + } + + public default void earlyEOF() + { + } } /* ------------------------------------------------------------------------------- */ @SuppressWarnings("serial") private static class IllegalCharacterException extends IllegalArgumentException { - private IllegalCharacterException(State state,byte ch,ByteBuffer buffer) + private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) { super(String.format("Illegal character 0x%X",ch)); // Bug #460642 - don't reveal buffers to end user diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartInputStreamTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java similarity index 88% rename from jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartInputStreamTest.java rename to jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java index cf8dd525289..c7b558108cb 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartInputStreamTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java @@ -44,7 +44,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.Part; -import org.eclipse.jetty.http.MultiPartInputStreamParser.MultiPart; +import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.IO; import org.hamcrest.Matchers; @@ -55,7 +55,7 @@ import org.junit.Test; * * */ -public class MultiPartInputStreamTest +public class MultiPartFormInputStreamTest { private static final String FILENAME = "stuff.txt"; protected String _contentType = "multipart/form-data, boundary=AaB03x"; @@ -63,7 +63,7 @@ public class MultiPartInputStreamTest protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); protected File _tmpDir = new File(_dirname); - public MultiPartInputStreamTest () + public MultiPartFormInputStreamTest () { _tmpDir.deleteOnExit(); } @@ -82,7 +82,7 @@ public class MultiPartInputStreamTest + "\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), "multipart/form-data, boundary="+boundary, config, _tmpDir); @@ -116,7 +116,7 @@ public class MultiPartInputStreamTest "--" + boundary + "--" + delimiter; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), "multipart/form-data, boundary="+boundary, config, _tmpDir); @@ -139,7 +139,7 @@ public class MultiPartInputStreamTest "--" + boundary + "--" + delimiter; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), "multipart/form-data, boundary="+boundary, config, _tmpDir); @@ -178,7 +178,7 @@ public class MultiPartInputStreamTest "----\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), "multipart/form-data", config, _tmpDir); @@ -213,7 +213,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), "Content-type: text/plain", config, _tmpDir); @@ -228,7 +228,7 @@ public class MultiPartInputStreamTest String body = ""; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), _contentType, config, _tmpDir); @@ -277,7 +277,7 @@ public class MultiPartInputStreamTest }; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(is, + MultiPartFormInputStream mpis = new MultiPartFormInputStream(is, _contentType, config, _tmpDir); @@ -296,7 +296,7 @@ public class MultiPartInputStreamTest MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), _contentType, config, _tmpDir); @@ -319,7 +319,7 @@ public class MultiPartInputStreamTest String whitespace = " "; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(whitespace.getBytes()), _contentType, config, _tmpDir); @@ -353,7 +353,7 @@ public class MultiPartInputStreamTest MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), _contentType, config, _tmpDir); @@ -394,7 +394,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(body.getBytes()), _contentType, config, _tmpDir); @@ -422,7 +422,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), _contentType, config, _tmpDir); @@ -436,7 +436,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), _contentType, config, _tmpDir); @@ -459,7 +459,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), _contentType, config, _tmpDir); @@ -494,7 +494,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), _contentType, config, _tmpDir); @@ -516,7 +516,7 @@ public class MultiPartInputStreamTest throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(_multi.getBytes()), _contentType, config, _tmpDir); @@ -527,9 +527,8 @@ public class MultiPartInputStreamTest parts = mpis.getParts(); //caused parsing fail("stuff.txt should have been larger than maxFileSize"); } - catch (IllegalStateException e) + catch (Throwable e) { - e.printStackTrace(); assertTrue(e.getMessage().startsWith("Multipart Mime part")); } @@ -551,7 +550,7 @@ public class MultiPartInputStreamTest public void testPartFileNotDeleted () throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), _contentType, config, _tmpDir); @@ -559,7 +558,7 @@ public class MultiPartInputStreamTest Collection parts = mpis.getParts(); MultiPart part = (MultiPart)mpis.getPart("stuff"); - File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile(); + File stuff = ((MultiPartFormInputStream.MultiPart)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"); @@ -574,7 +573,7 @@ public class MultiPartInputStreamTest public void testPartTmpFileDeletion () throws Exception { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()), _contentType, config, _tmpDir); @@ -582,7 +581,7 @@ public class MultiPartInputStreamTest Collection parts = mpis.getParts(); MultiPart part = (MultiPart)mpis.getPart("stuff"); - File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile(); + File stuff = ((MultiPartFormInputStream.MultiPart)part).getFile(); assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file assertThat (stuff.exists(), is(true)); part.cleanUp(); @@ -604,7 +603,7 @@ public class MultiPartInputStreamTest "\r\n--AaB03x--\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), _contentType, config, _tmpDir); @@ -641,7 +640,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), _contentType, config, _tmpDir); @@ -688,7 +687,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), _contentType, config, _tmpDir); @@ -729,7 +728,7 @@ public class MultiPartInputStreamTest } MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(baos.toByteArray()), _contentType, config, _tmpDir); @@ -758,7 +757,7 @@ public class MultiPartInputStreamTest "--TheBoundary--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(str.getBytes()), contentType, config, _tmpDir); @@ -780,14 +779,14 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), _contentType, config, _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg")); + assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg")); } @Test @@ -802,14 +801,14 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), _contentType, config, _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); + assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); } @Test @@ -823,14 +822,14 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contents.getBytes()), _contentType, config, _tmpDir); mpis.setDeleteOnExit(true); Collection parts = mpis.getParts(); assertThat(parts.size(), is(1)); - assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); + assertThat(((MultiPartFormInputStream.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt")); } public void testMulti () @@ -862,7 +861,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; //all default values for multipartconfig, ie file size threshold 0 MultipartConfigElement config = new MultipartConfigElement(_dirname); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(s.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(s.getBytes()), _contentType, config, _tmpDir); @@ -871,11 +870,11 @@ public class MultiPartInputStreamTest 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 = ((MultiPartInputStreamParser.MultiPart)field1).getFile(); + File f = ((MultiPartFormInputStream.MultiPart)field1).getFile(); assertThat(f,notNullValue()); // longer than 100 bytes, should already be a tmp file Part stuff = mpis.getPart("stuff"); - f = ((MultiPartInputStreamParser.MultiPart)stuff).getFile(); //should only be in memory, no filename + f = ((MultiPartFormInputStream.MultiPart)stuff).getFile(); //should only be in memory, no filename assertThat(f, nullValue()); } @@ -883,7 +882,7 @@ public class MultiPartInputStreamTest private void testMulti(String filename) throws IOException, ServletException, InterruptedException { MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()), _contentType, config, _tmpDir); @@ -899,9 +898,9 @@ public class MultiPartInputStreamTest assertEquals("Joe Blow", new String(os.toByteArray())); assertEquals(8, field1.getSize()); - assertNotNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//in internal buffer + assertNotNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//in internal buffer field1.write("field1.txt"); - assertNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//no longer in internal buffer + assertNull(((MultiPartFormInputStream.MultiPart)field1).getBytes());//no longer in internal buffer File f = new File (_dirname+File.separator+"field1.txt"); assertTrue(f.exists()); field1.write("another_field1.txt"); //write after having already written @@ -921,7 +920,7 @@ public class MultiPartInputStreamTest assertThat(stuff.getHeaderNames().size(),is(2)); assertThat(stuff.getSize(),is(51L)); - File tmpfile = ((MultiPartInputStreamParser.MultiPart)stuff).getFile(); + 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)); @@ -958,7 +957,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(sameNames.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(sameNames.getBytes()), _contentType, config, _tmpDir); @@ -997,7 +996,7 @@ public class MultiPartInputStreamTest "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), _contentType, config, _tmpDir); @@ -1041,7 +1040,7 @@ public class MultiPartInputStreamTest "truth=3Dbeauty" + "\r\n"+ "--AaB03x--\r\n"; MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); - MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), + MultiPartFormInputStream mpis = new MultiPartFormInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()), _contentType, config, _tmpDir); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java index 23c55ddb90c..c3378d9e794 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java @@ -55,7 +55,15 @@ import org.eclipse.jetty.util.log.Logger; * MultiPartInputStream * * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings. + * + * Non Compliance warnings are documented by the method {@link #getNonComplianceWarnings()} + * + * @deprecated Replaced by {@link org.eclipse.jetty.http#MultiPartFormInputStream} + * The code for MultiPartInputStream is slower than its replacement MultiPartFormInputStream. However + * this class accepts formats non compliant the RFC that the new MultiPartFormInputStream does not accept. + * */ +@Deprecated public class MultiPartInputStreamParser { private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class); @@ -71,7 +79,7 @@ public class MultiPartInputStreamParser protected boolean _deleteOnExit; protected boolean _writeFilesWithFilenames; - EnumSet nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class); + private EnumSet nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class); public enum NonCompliance { CR_TERMINATION, @@ -80,15 +88,12 @@ public class MultiPartInputStreamParser BASE64_TRANSFER_ENCODING, QUOTED_PRINTABLE_TRANSFER_ENCODING } + + /** + * @return an EnumSet of non compliances with the RFC that were accepted by this parser + */ public EnumSet getNonComplianceWarnings() - { - EnumSet term = ((ReadLineInputStream)_in).getLineTerminations(); - - if(term.contains(Termination.CR)) - nonComplianceWarnings.add(NonCompliance.CR_TERMINATION); - if(term.contains(Termination.LF)) - nonComplianceWarnings.add(NonCompliance.LF_TERMINATION); - + { return nonComplianceWarnings; } @@ -838,6 +843,13 @@ public class MultiPartInputStreamParser { while(line!=null) line=((ReadLineInputStream)_in).readLine(); + + EnumSet term = ((ReadLineInputStream)_in).getLineTerminations(); + + if(term.contains(Termination.CR)) + nonComplianceWarnings.add(NonCompliance.CR_TERMINATION); + if(term.contains(Termination.LF)) + nonComplianceWarnings.add(NonCompliance.LF_TERMINATION); } else throw new IOException("Incomplete parts"); @@ -846,6 +858,7 @@ public class MultiPartInputStreamParser { _err = e; } + } public void setDeleteOnExit(boolean deleteOnExit) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java index a6acd0c391f..427c8a65e91 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance; * * Read from an input stream, accepting CR/LF, LF or just CR. */ +@Deprecated public class ReadLineInputStream extends BufferedInputStream { boolean _seenCRLF; diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java index d9246b225f6..dd623cd7072 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java @@ -47,7 +47,6 @@ import javax.servlet.http.Part; import org.eclipse.jetty.util.MultiPartInputStreamParser.MultiPart; import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance; -import org.hamcrest.Matchers; import org.junit.Test; /** @@ -55,6 +54,7 @@ import org.junit.Test; * * */ +@SuppressWarnings("deprecation") public class MultiPartInputStreamTest { private static final String FILENAME = "stuff.txt";