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 c2d1d14fe29..35d4f2a9f6c 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 @@ -35,7 +35,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletInputStream; @@ -73,6 +72,7 @@ public class MultiPartFormInputStream protected File _contextTmpDir; protected boolean _deleteOnExit; protected boolean _writeFilesWithFilenames; + protected boolean _parsed; public class MultiPart implements Part { @@ -384,17 +384,37 @@ public class MultiPartFormInputStream if (((ServletInputStream)in).isFinished()) { _parts = EMPTY_MAP; + _parsed = true; return; } } _in = new BufferedInputStream(in); } + /** + * @return whether the list of parsed parts is empty + */ + public boolean isEmpty() + { + if (_parts == null) + return true; + + Collection> values = _parts.values(); + for (List partList : values) + { + if(partList.size() != 0) + return false; + } + + return true; + } + /** * Get the already parsed parts. * * @return the parts that were parsed */ + @Deprecated public Collection getParsedParts() { if (_parts == null) @@ -412,14 +432,23 @@ public class MultiPartFormInputStream /** * Delete any tmp storage for parts, and clear out the parts list. - * - * @throws MultiException - * if unable to delete the parts */ - public void deleteParts() throws MultiException + public void deleteParts() { - Collection parts = getParsedParts(); + if (!_parsed) + return; + + Collection parts; + try + { + parts = getParts(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } MultiException err = new MultiException(); + for (Part p : parts) { try @@ -433,7 +462,7 @@ public class MultiPartFormInputStream } _parts.clear(); - err.ifExceptionThrowMulti(); + err.ifExceptionThrowRuntime(); } /** @@ -445,7 +474,8 @@ public class MultiPartFormInputStream */ public Collection getParts() throws IOException { - parse(); + if (!_parsed) + parse(); throwIfError(); Collection> values = _parts.values(); @@ -469,7 +499,8 @@ public class MultiPartFormInputStream */ public Part getPart(String name) throws IOException { - parse(); + if(!_parsed) + parse(); throwIfError(); return _parts.getValue(name,0); } @@ -500,8 +531,10 @@ public class MultiPartFormInputStream protected void parse() { // have we already parsed the input? - if (_parts != null || _err != null) + if (_parsed) return; + _parsed = true; + try { @@ -612,7 +645,6 @@ public class MultiPartFormInputStream class Handler implements MultiPartParser.Handler { - private MultiPart _part = null; private String contentDisposition = null; private String contentType = null; @@ -673,18 +705,16 @@ public class MultiPartFormInputStream // Check disposition if (!form_data) - { - return false; - } + throw new IOException("Part not form-data"); + // 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; - } + throw new IOException("No name in part"); + // create the new part _part = new MultiPart(name,filename); @@ -766,12 +796,6 @@ public class MultiPartFormInputStream contentType = null; headers = new MultiMap<>(); } - - @Override - public String toString() { - return("contentDisposition: "+contentDisposition+" contentType:"+contentType); - } - } public void setDeleteOnExit(boolean deleteOnExit) 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 f48b4d4a309..c12109383a7 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,6 +24,7 @@ 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; 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 bc1c77b8fad..009be1af293 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 @@ -19,18 +19,18 @@ package org.eclipse.jetty.http; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import org.eclipse.jetty.http.MultiPartParser.State; import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.Matchers; -import org.junit.Ignore; import org.junit.Test; public class MultiPartParserTest diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 79f0a8d7ea0..c2fbf69bdd8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -51,7 +51,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque { private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class); private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); - private static final String ATTR_COMPLIANCE_VIOLATIONS = "org.eclipse.jetty.http.compliance.violations"; + public static final String ATTR_COMPLIANCE_VIOLATIONS = "org.eclipse.jetty.http.compliance.violations"; private final HttpFields _fields = new HttpFields(); private final MetaData.Request _metadata = new MetaData.Request(_fields); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartCleanerListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartCleanerListener.java index 2d176b37c2d..af7310c706c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartCleanerListener.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartCleanerListener.java @@ -38,19 +38,19 @@ public class MultiPartCleanerListener implements ServletRequestListener public void requestDestroyed(ServletRequestEvent sre) { //Clean up any tmp files created by MultiPartInputStream - Request.MultiPartInputStream mpis = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); - if (mpis != null) + Request.MultiParts parts = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS); + if (parts != null) { - ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT); + ContextHandler.Context context = parts.getContext(); //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files if (context == sre.getServletContext()) { try { - mpis.deleteParts(); + parts.close(); } - catch (MultiException e) + catch (Exception e) { sre.getServletContext().log("Errors deleting multipart tmp files", e); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 3dac341d7d4..9a4e4f49dea 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.server; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -34,6 +35,7 @@ import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.Enumeration; import java.util.EventListener; import java.util.List; @@ -84,9 +86,9 @@ import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiPartInputStreamParser; +import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.UrlEncoded; @@ -136,8 +138,7 @@ import org.eclipse.jetty.util.log.Logger; public class Request implements HttpServletRequest { public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig"; - public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream"; - public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext"; + public static final String __MULTIPARTS = "org.eclipse.jetty.multiPartInputStream"; private static final Logger LOG = Log.getLogger(Request.class); private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault()); @@ -216,7 +217,7 @@ public class Request implements HttpServletRequest private HttpSession _session; private SessionHandler _sessionHandler; private long _timeStamp; - private MultiPartInputStream _multiPartInputStream; //if the request is a multi-part mime + private MultiParts _multiParts; //if the request is a multi-part mime private AsyncContextState _async; /* ------------------------------------------------------------ */ @@ -476,7 +477,7 @@ public class Request implements HttpServletRequest } else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) && getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && - _multiPartInputStream == null) + _multiParts == null) { try { @@ -1869,7 +1870,7 @@ public class Request implements HttpServletRequest _parameters = null; _contentParamsExtracted = false; _inputState = __NONE; - _multiPartInputStream = null; + _multiParts = null; _remote=null; _input.recycle(); } @@ -2310,7 +2311,7 @@ public class Request implements HttpServletRequest { getParts(); - return _multiPartInputStream.getPart(name); + return _multiParts.getPart(name); } /* ------------------------------------------------------------ */ @@ -2325,26 +2326,52 @@ public class Request implements HttpServletRequest private Collection getParts(MultiMap params) throws IOException, ServletException { - if (_multiPartInputStream == null) - _multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM); + if (_multiParts == null) + _multiParts = (MultiParts)getAttribute(__MULTIPARTS); - if (_multiPartInputStream == null) + if (_multiParts == null) { MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT); if (config == null) throw new IllegalStateException("No multipart config for servlet"); - _multiPartInputStream = new MultiPartInputStream(getInputStream(), + _multiParts = newMultiParts(getInputStream(), getContentType(), config, - (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), + true); - setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream); - setAttribute(__MULTIPART_CONTEXT, _context); - Collection parts = _multiPartInputStream.getParts(); //causes parsing + setAttribute(__MULTIPARTS, _multiParts); + Collection parts = _multiParts.getParts(); //causes parsing + + String _charset_ = null; + Part charsetPart = _multiParts.getPart("_charset_"); + if(charsetPart != null) + { + try (InputStream is = charsetPart.getInputStream()) + { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IO.copy(is, os); + _charset_ = new String(os.toByteArray(),StandardCharsets.UTF_8); + } + } + + // charset should be: + // 1. the charset set in the parts content type; else + // 2. the default charset set in the _charset_ part; else + // 3. the default charset set in the request.setCharacterEncoding; else + // 4. the default charset set to UTF_8 + Charset defaultCharset; + if (_charset_ != null) + defaultCharset = Charset.forName(_charset_); + else if (getCharacterEncoding() != null) + defaultCharset = Charset.forName(getCharacterEncoding()); + else + defaultCharset = StandardCharsets.UTF_8; + ByteArrayOutputStream os = null; for (Part p:parts) { - if (_multiPartInputStream.getContentDispositionFilename(p) == null) + if (p.getSubmittedFileName() == null) { // Servlet Spec 3.0 pg 23, parts without filename must be put into params. String charset = null; @@ -2356,7 +2383,8 @@ public class Request implements HttpServletRequest if (os == null) os = new ByteArrayOutputStream(); IO.copy(is, os); - String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset)); + + String content=new String(os.toByteArray(),charset==null?defaultCharset:Charset.forName(charset)); if (_contentParameters == null) _contentParameters = params == null ? new MultiMap<>() : params; _contentParameters.add(p.getName(), content); @@ -2366,10 +2394,28 @@ public class Request implements HttpServletRequest } } - return _multiPartInputStream.getParts(); + return _multiParts.getParts(); } + private MultiParts newMultiParts(ServletInputStream inputStream, String contentType, MultipartConfigElement config, Object object, boolean useNewParser) throws IOException + { + MultiParts multiParts; + + if(useNewParser) + { + multiParts = new MultiPartsHttpParser(getInputStream(), getContentType(), config, + (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + } + else + { + multiParts = new MultiPartsUtilParser(getInputStream(), getContentType(), config, + (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + } + + return multiParts; + } + /* ------------------------------------------------------------ */ @Override public void login(String username, String password) throws ServletException @@ -2477,83 +2523,144 @@ public class Request implements HttpServletRequest * Used to switch between the old and new implementation of MultiPart Form InputStream Parsing. * The new implementation is prefered will be used as default unless specified otherwise constructor. */ - @SuppressWarnings("deprecation") - public class MultiPartInputStream + public interface MultiParts extends Closeable { - private boolean usingNewParser = true; - private MultiPartFormInputStream _newParser = null; - private MultiPartInputStreamParser _oldParser = null; - - public MultiPartInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) - { - this(in, contentType, config, contextTmpDir, false); - } + public Collection getParts(); + public Part getPart(String name); + public boolean isEmpty(); + public ContextHandler.Context getContext(); + } + + + public class MultiPartsHttpParser implements MultiParts + { + private final MultiPartFormInputStream _httpParser; + private final ContextHandler.Context _context; - public MultiPartInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, boolean useOldParser) + public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) throws IOException { - if(useOldParser) - usingNewParser = false; - else - usingNewParser = true; - - if(usingNewParser) - _newParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir); - else - _oldParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir); + _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir); + _context = Request.this._context; + _httpParser.getParts(); } - public Collection getParts() throws IOException { - Collection parts = null; - - if(usingNewParser) - parts = _newParser.getParts(); - else - parts = _oldParser.getParts(); - - return parts; - } - - public Part getPart(String name) throws IOException { - Part part = null; - - if(usingNewParser) - part = _newParser.getPart(name); - else - part = _oldParser.getPart(name); - - return part; - } - - public String getContentDispositionFilename(Part p) + @Override + public Collection getParts() { - String contentDisposition = null; + try + { + return _httpParser.getParts(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } - if(usingNewParser) - contentDisposition = ((MultiPartFormInputStream.MultiPart)p).getContentDispositionFilename(); - else - contentDisposition = ((MultiPartInputStreamParser.MultiPart)p).getContentDispositionFilename(); - - return contentDisposition; + @Override + public Part getPart(String name) { + try + { + return _httpParser.getPart(name); + } + catch (IOException e) + { + throw new RuntimeException(e); + } } - public void deleteParts() throws MultiException + @Override + public void close() { - if(usingNewParser) - _newParser.deleteParts(); - else - _oldParser.deleteParts(); + _httpParser.deleteParts(); } - public Collection getParsedParts() + @Override + public boolean isEmpty() { - Collection parsedParts = null; - - if(usingNewParser) - parsedParts = _newParser.getParsedParts(); - else - parsedParts = _oldParser.getParsedParts(); - - return parsedParts; + return _httpParser.isEmpty(); } + + @Override + public Context getContext() + { + return _context; + } + + } + + + @SuppressWarnings("deprecation") + public class MultiPartsUtilParser implements MultiParts + { + private final MultiPartInputStreamParser _utilParser; + private final ContextHandler.Context _context; + + public MultiPartsUtilParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) throws IOException + { + _utilParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir); + _context = Request.this._context; + _utilParser.getParts(); + + EnumSet nonComplianceWarnings = _utilParser.getNonComplianceWarnings(); + if (!nonComplianceWarnings.isEmpty()) + { + @SuppressWarnings("unchecked") + List violations = (List)getAttribute(HttpChannelOverHttp.ATTR_COMPLIANCE_VIOLATIONS); + if (violations==null) + { + violations = new ArrayList<>(); + setAttribute(HttpChannelOverHttp.ATTR_COMPLIANCE_VIOLATIONS,violations); + } + + for(NonCompliance nc : nonComplianceWarnings) + violations.add(nc.name()+": "+nc.getURL()); + } + } + + @Override + public Collection getParts() + { + try + { + return _utilParser.getParts(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public Part getPart(String name) + { + try + { + return _utilParser.getPart(name); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void close() + { + _utilParser.deleteParts(); + } + + @Override + public boolean isEmpty() + { + return _utilParser.getParsedParts().isEmpty(); + } + + @Override + public Context getContext() + { + return _context; + } + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index eeefa299e7f..d788de882c8 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -44,7 +44,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Locale; -import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -70,7 +69,6 @@ import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.MultiPartInputStreamParser; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StacklessLogging; @@ -162,7 +160,7 @@ public class RequestTest @Override public boolean check(HttpServletRequest request,HttpServletResponse response) { - Map map = request.getParameterMap(); + request.getParameterMap(); // should have thrown a BadMessageException return false; } @@ -189,7 +187,7 @@ public class RequestTest @Override public boolean check(HttpServletRequest request,HttpServletResponse response) { - Map map = request.getParameterMap(); + request.getParameterMap(); // should have thrown a BadMessageException return false; } @@ -364,12 +362,12 @@ public class RequestTest @Override public void requestDestroyed(ServletRequestEvent sre) { - Request.MultiPartInputStream m = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); - ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT); + Request.MultiParts m = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS); assertNotNull (m); + ContextHandler.Context c = m.getContext(); assertNotNull (c); assertTrue(c == sre.getServletContext()); - assertTrue(!m.getParsedParts().isEmpty()); + assertTrue(!m.isEmpty()); assertTrue(testTmpDir.list().length == 2); super.requestDestroyed(sre); String[] files = testTmpDir.list(); @@ -401,7 +399,7 @@ public class RequestTest multipart; String responses=_connector.getResponse(request); - // System.err.println(responses); + //System.err.println(responses); assertTrue(responses.startsWith("HTTP/1.1 200")); } @@ -426,9 +424,9 @@ public class RequestTest @Override public void requestDestroyed(ServletRequestEvent sre) { - Request.MultiPartInputStream m = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); - ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT); + Request.MultiParts m = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS); assertNotNull (m); + ContextHandler.Context c = m.getContext(); assertNotNull (c); assertTrue(c == sre.getServletContext()); super.requestDestroyed(sre); 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 2dd81111fa0..131a5587c78 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 @@ -78,15 +78,28 @@ public class MultiPartInputStreamParser protected File _contextTmpDir; protected boolean _deleteOnExit; protected boolean _writeFilesWithFilenames; + protected boolean _parsed; private EnumSet nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class); public enum NonCompliance { - CR_TERMINATION, - LF_TERMINATION, - NO_INITIAL_CRLF, - BASE64_TRANSFER_ENCODING, - QUOTED_PRINTABLE_TRANSFER_ENCODING + CR_LINE_TERMINATION("https://tools.ietf.org/html/rfc2046#section-4.1.1"), + LF_LINE_TERMINATION("https://tools.ietf.org/html/rfc2046#section-4.1.1"), + NO_CRLF_AFTER_PREAMBLE("https://tools.ietf.org/html/rfc2046#section-5.1.1"), + BASE64_TRANSFER_ENCODING("https://tools.ietf.org/html/rfc7578#section-4.7"), + QUOTED_PRINTABLE_TRANSFER_ENCODING("https://tools.ietf.org/html/rfc7578#section-4.7"); + + final String _rfcRef; + + NonCompliance(String rfcRef) + { + _rfcRef = rfcRef; + } + + public String getURL() + { + return _rfcRef; + } } /** @@ -414,6 +427,7 @@ public class MultiPartInputStreamParser if (((ServletInputStream)in).isFinished()) { _parts = EMPTY_MAP; + _parsed = true; return; } } @@ -441,12 +455,12 @@ public class MultiPartInputStreamParser /** * Delete any tmp storage for parts, and clear out the parts list. - * - * @throws MultiException if unable to delete the parts */ public void deleteParts () - throws MultiException { + if(!_parsed) + return; + Collection parts = getParsedParts(); MultiException err = new MultiException(); for (Part p:parts) @@ -462,7 +476,7 @@ public class MultiPartInputStreamParser } _parts.clear(); - err.ifExceptionThrowMulti(); + err.ifExceptionThrowRuntime(); } @@ -475,7 +489,8 @@ public class MultiPartInputStreamParser public Collection getParts() throws IOException { - parse(); + if(!_parsed) + parse(); throwIfError(); @@ -500,7 +515,8 @@ public class MultiPartInputStreamParser public Part getPart(String name) throws IOException { - parse(); + if(_parsed) + parse(); throwIfError(); return _parts.getValue(name, 0); } @@ -530,8 +546,9 @@ public class MultiPartInputStreamParser protected void parse () { //have we already parsed the input? - if (_parts != null || _err != null) + if (_parsed) return; + _parsed = true; //initialize @@ -616,7 +633,7 @@ public class MultiPartInputStreamParser // check compliance of preamble if (Character.isWhitespace(untrimmed.charAt(0))) - nonComplianceWarnings.add(NonCompliance.NO_INITIAL_CRLF); + nonComplianceWarnings.add(NonCompliance.NO_CRLF_AFTER_PREAMBLE); // Read each part boolean lastPart=false; @@ -847,9 +864,9 @@ public class MultiPartInputStreamParser EnumSet term = ((ReadLineInputStream)_in).getLineTerminations(); if(term.contains(Termination.CR)) - nonComplianceWarnings.add(NonCompliance.CR_TERMINATION); + nonComplianceWarnings.add(NonCompliance.CR_LINE_TERMINATION); if(term.contains(Termination.LF)) - nonComplianceWarnings.add(NonCompliance.LF_TERMINATION); + nonComplianceWarnings.add(NonCompliance.LF_LINE_TERMINATION); } else throw new IOException("Incomplete parts"); 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 dd623cd7072..2463d398f78 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 @@ -378,7 +378,7 @@ public class MultiPartInputStreamTest IO.copy(stuff.getInputStream(), baos); assertTrue(baos.toString("US-ASCII").contains("aaaa")); - assertEquals(EnumSet.of(NonCompliance.LF_TERMINATION), mpis.getNonComplianceWarnings()); + assertEquals(EnumSet.of(NonCompliance.LF_LINE_TERMINATION), mpis.getNonComplianceWarnings()); } @@ -420,7 +420,7 @@ public class MultiPartInputStreamTest IO.copy(stuff.getInputStream(), baos); assertTrue(baos.toString("US-ASCII").contains("bbbbb")); - assertEquals(EnumSet.of(NonCompliance.NO_INITIAL_CRLF), mpis.getNonComplianceWarnings()); + assertEquals(EnumSet.of(NonCompliance.NO_CRLF_AFTER_PREAMBLE), mpis.getNonComplianceWarnings()); } @@ -631,7 +631,7 @@ public class MultiPartInputStreamTest IO.copy(p2.getInputStream(), baos); assertThat(baos.toString("UTF-8"), is("Other")); - assertEquals(EnumSet.of(NonCompliance.LF_TERMINATION), mpis.getNonComplianceWarnings()); + assertEquals(EnumSet.of(NonCompliance.LF_LINE_TERMINATION), mpis.getNonComplianceWarnings()); } @Test @@ -671,7 +671,7 @@ public class MultiPartInputStreamTest IO.copy(p2.getInputStream(), baos); assertThat(baos.toString("UTF-8"), is("Other")); - assertEquals(EnumSet.of(NonCompliance.CR_TERMINATION), mpis.getNonComplianceWarnings()); + assertEquals(EnumSet.of(NonCompliance.CR_LINE_TERMINATION), mpis.getNonComplianceWarnings()); } @Test @@ -710,7 +710,7 @@ public class MultiPartInputStreamTest IO.copy(p2.getInputStream(), baos); assertThat(baos.toString("UTF-8"), is("Other")); - assertEquals(EnumSet.of(NonCompliance.CR_TERMINATION), mpis.getNonComplianceWarnings()); + assertEquals(EnumSet.of(NonCompliance.CR_LINE_TERMINATION), mpis.getNonComplianceWarnings()); } @Test