Resolved Charset Issues and Reworked Request.MultiPartInputStream

Changed Request.MultiPartInputStream to an interface called MultiParts where there is an implementation for both the HTTP and UTIL parsers.

Resolved some issues with default charsets in regards to request.setCharacterEncoding and the _charset_ part for issue #2398.

Changed HTTP parser to operate the same as UTIL parser in situtions with parts not of type form-data or without name field. HTTP parser was ignoring these parts, UTIL parser was throwing exceptions.

Replaced the context attribute with a field in MultiParts.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2018-04-03 15:24:54 +10:00
parent be2d6ebb29
commit 13b15e3566
9 changed files with 291 additions and 144 deletions

View File

@ -35,7 +35,6 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.servlet.MultipartConfigElement; import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
@ -73,6 +72,7 @@ public class MultiPartFormInputStream
protected File _contextTmpDir; protected File _contextTmpDir;
protected boolean _deleteOnExit; protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames; protected boolean _writeFilesWithFilenames;
protected boolean _parsed;
public class MultiPart implements Part public class MultiPart implements Part
{ {
@ -384,17 +384,37 @@ public class MultiPartFormInputStream
if (((ServletInputStream)in).isFinished()) if (((ServletInputStream)in).isFinished())
{ {
_parts = EMPTY_MAP; _parts = EMPTY_MAP;
_parsed = true;
return; return;
} }
} }
_in = new BufferedInputStream(in); _in = new BufferedInputStream(in);
} }
/**
* @return whether the list of parsed parts is empty
*/
public boolean isEmpty()
{
if (_parts == null)
return true;
Collection<List<Part>> values = _parts.values();
for (List<Part> partList : values)
{
if(partList.size() != 0)
return false;
}
return true;
}
/** /**
* Get the already parsed parts. * Get the already parsed parts.
* *
* @return the parts that were parsed * @return the parts that were parsed
*/ */
@Deprecated
public Collection<Part> getParsedParts() public Collection<Part> getParsedParts()
{ {
if (_parts == null) if (_parts == null)
@ -412,14 +432,23 @@ public class MultiPartFormInputStream
/** /**
* Delete any tmp storage for parts, and clear out the parts list. * 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<Part> parts = getParsedParts(); if (!_parsed)
return;
Collection<Part> parts;
try
{
parts = getParts();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
MultiException err = new MultiException(); MultiException err = new MultiException();
for (Part p : parts) for (Part p : parts)
{ {
try try
@ -433,7 +462,7 @@ public class MultiPartFormInputStream
} }
_parts.clear(); _parts.clear();
err.ifExceptionThrowMulti(); err.ifExceptionThrowRuntime();
} }
/** /**
@ -445,7 +474,8 @@ public class MultiPartFormInputStream
*/ */
public Collection<Part> getParts() throws IOException public Collection<Part> getParts() throws IOException
{ {
parse(); if (!_parsed)
parse();
throwIfError(); throwIfError();
Collection<List<Part>> values = _parts.values(); Collection<List<Part>> values = _parts.values();
@ -469,7 +499,8 @@ public class MultiPartFormInputStream
*/ */
public Part getPart(String name) throws IOException public Part getPart(String name) throws IOException
{ {
parse(); if(!_parsed)
parse();
throwIfError(); throwIfError();
return _parts.getValue(name,0); return _parts.getValue(name,0);
} }
@ -500,8 +531,10 @@ public class MultiPartFormInputStream
protected void parse() protected void parse()
{ {
// have we already parsed the input? // have we already parsed the input?
if (_parts != null || _err != null) if (_parsed)
return; return;
_parsed = true;
try try
{ {
@ -612,7 +645,6 @@ public class MultiPartFormInputStream
class Handler implements MultiPartParser.Handler class Handler implements MultiPartParser.Handler
{ {
private MultiPart _part = null; private MultiPart _part = null;
private String contentDisposition = null; private String contentDisposition = null;
private String contentType = null; private String contentType = null;
@ -673,18 +705,16 @@ public class MultiPartFormInputStream
// Check disposition // Check disposition
if (!form_data) if (!form_data)
{ throw new IOException("Part not form-data");
return false;
}
// It is valid for reset and submit buttons to have an empty name. // 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. // 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 // 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 // field, with name as the empty string. So, only continue this loop if we
// have not yet seen a name field. // have not yet seen a name field.
if (name == null) if (name == null)
{ throw new IOException("No name in part");
return false;
}
// create the new part // create the new part
_part = new MultiPart(name,filename); _part = new MultiPart(name,filename);
@ -766,12 +796,6 @@ public class MultiPartFormInputStream
contentType = null; contentType = null;
headers = new MultiMap<>(); headers = new MultiMap<>();
} }
@Override
public String toString() {
return("contentDisposition: "+contentDisposition+" contentType:"+contentType);
}
} }
public void setDeleteOnExit(boolean deleteOnExit) public void setDeleteOnExit(boolean deleteOnExit)

View File

@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;

View File

@ -19,18 +19,18 @@
package org.eclipse.jetty.http; package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.is; 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.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import org.eclipse.jetty.http.MultiPartParser.State; import org.eclipse.jetty.http.MultiPartParser.State;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class MultiPartParserTest public class MultiPartParserTest

View File

@ -51,7 +51,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
{ {
private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class); private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); 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 HttpFields _fields = new HttpFields();
private final MetaData.Request _metadata = new MetaData.Request(_fields); private final MetaData.Request _metadata = new MetaData.Request(_fields);

View File

@ -38,19 +38,19 @@ public class MultiPartCleanerListener implements ServletRequestListener
public void requestDestroyed(ServletRequestEvent sre) public void requestDestroyed(ServletRequestEvent sre)
{ {
//Clean up any tmp files created by MultiPartInputStream //Clean up any tmp files created by MultiPartInputStream
Request.MultiPartInputStream mpis = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); Request.MultiParts parts = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS);
if (mpis != null) 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 //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
if (context == sre.getServletContext()) if (context == sre.getServletContext())
{ {
try try
{ {
mpis.deleteParts(); parts.close();
} }
catch (MultiException e) catch (Exception e)
{ {
sre.getServletContext().log("Errors deleting multipart tmp files", e); sre.getServletContext().log("Errors deleting multipart tmp files", e);
} }

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.server;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -34,6 +35,7 @@ import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.EventListener; import java.util.EventListener;
import java.util.List; 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.Attributes;
import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStreamParser; import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.MultiPartInputStreamParser.NonCompliance;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.UrlEncoded;
@ -136,8 +138,7 @@ import org.eclipse.jetty.util.log.Logger;
public class Request implements HttpServletRequest public class Request implements HttpServletRequest
{ {
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig"; 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 __MULTIPARTS = "org.eclipse.jetty.multiPartInputStream";
public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
private static final Logger LOG = Log.getLogger(Request.class); private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault()); private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@ -216,7 +217,7 @@ public class Request implements HttpServletRequest
private HttpSession _session; private HttpSession _session;
private SessionHandler _sessionHandler; private SessionHandler _sessionHandler;
private long _timeStamp; 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; private AsyncContextState _async;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -476,7 +477,7 @@ public class Request implements HttpServletRequest
} }
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) && else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) &&
getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
_multiPartInputStream == null) _multiParts == null)
{ {
try try
{ {
@ -1869,7 +1870,7 @@ public class Request implements HttpServletRequest
_parameters = null; _parameters = null;
_contentParamsExtracted = false; _contentParamsExtracted = false;
_inputState = __NONE; _inputState = __NONE;
_multiPartInputStream = null; _multiParts = null;
_remote=null; _remote=null;
_input.recycle(); _input.recycle();
} }
@ -2310,7 +2311,7 @@ public class Request implements HttpServletRequest
{ {
getParts(); getParts();
return _multiPartInputStream.getPart(name); return _multiParts.getPart(name);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -2325,26 +2326,52 @@ public class Request implements HttpServletRequest
private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
{ {
if (_multiPartInputStream == null) if (_multiParts == null)
_multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM); _multiParts = (MultiParts)getAttribute(__MULTIPARTS);
if (_multiPartInputStream == null) if (_multiParts == null)
{ {
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT); MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
if (config == null) if (config == null)
throw new IllegalStateException("No multipart config for servlet"); throw new IllegalStateException("No multipart config for servlet");
_multiPartInputStream = new MultiPartInputStream(getInputStream(), _multiParts = newMultiParts(getInputStream(),
getContentType(), config, 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(__MULTIPARTS, _multiParts);
setAttribute(__MULTIPART_CONTEXT, _context); Collection<Part> parts = _multiParts.getParts(); //causes parsing
Collection<Part> parts = _multiPartInputStream.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; ByteArrayOutputStream os = null;
for (Part p:parts) 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. // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
String charset = null; String charset = null;
@ -2356,7 +2383,8 @@ public class Request implements HttpServletRequest
if (os == null) if (os == null)
os = new ByteArrayOutputStream(); os = new ByteArrayOutputStream();
IO.copy(is, os); 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) if (_contentParameters == null)
_contentParameters = params == null ? new MultiMap<>() : params; _contentParameters = params == null ? new MultiMap<>() : params;
_contentParameters.add(p.getName(), content); _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 @Override
public void login(String username, String password) throws ServletException 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. * 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. * The new implementation is prefered will be used as default unless specified otherwise constructor.
*/ */
@SuppressWarnings("deprecation") public interface MultiParts extends Closeable
public class MultiPartInputStream
{ {
private boolean usingNewParser = true; public Collection<Part> getParts();
private MultiPartFormInputStream _newParser = null; public Part getPart(String name);
private MultiPartInputStreamParser _oldParser = null; public boolean isEmpty();
public ContextHandler.Context getContext();
public MultiPartInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) }
{
this(in, contentType, config, contextTmpDir, false);
} 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) _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir);
usingNewParser = false; _context = Request.this._context;
else _httpParser.getParts();
usingNewParser = true;
if(usingNewParser)
_newParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir);
else
_oldParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir);
} }
public Collection<Part> getParts() throws IOException { @Override
Collection<Part> parts = null; public Collection<Part> getParts()
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)
{ {
String contentDisposition = null; try
{
return _httpParser.getParts();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
if(usingNewParser) @Override
contentDisposition = ((MultiPartFormInputStream.MultiPart)p).getContentDispositionFilename(); public Part getPart(String name) {
else try
contentDisposition = ((MultiPartInputStreamParser.MultiPart)p).getContentDispositionFilename(); {
return _httpParser.getPart(name);
return contentDisposition; }
catch (IOException e)
{
throw new RuntimeException(e);
}
} }
public void deleteParts() throws MultiException @Override
public void close()
{ {
if(usingNewParser) _httpParser.deleteParts();
_newParser.deleteParts();
else
_oldParser.deleteParts();
} }
public Collection<Part> getParsedParts() @Override
public boolean isEmpty()
{ {
Collection<Part> parsedParts = null; return _httpParser.isEmpty();
if(usingNewParser)
parsedParts = _newParser.getParsedParts();
else
parsedParts = _oldParser.getParsedParts();
return parsedParts;
} }
@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<NonCompliance> nonComplianceWarnings = _utilParser.getNonComplianceWarnings();
if (!nonComplianceWarnings.isEmpty())
{
@SuppressWarnings("unchecked")
List<String> violations = (List<String>)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<Part> 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;
}
} }
} }

View File

@ -44,7 +44,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; 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.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
@ -162,7 +160,7 @@ public class RequestTest
@Override @Override
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
Map<String, String[]> map = request.getParameterMap(); request.getParameterMap();
// should have thrown a BadMessageException // should have thrown a BadMessageException
return false; return false;
} }
@ -189,7 +187,7 @@ public class RequestTest
@Override @Override
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
Map<String, String[]> map = request.getParameterMap(); request.getParameterMap();
// should have thrown a BadMessageException // should have thrown a BadMessageException
return false; return false;
} }
@ -364,12 +362,12 @@ public class RequestTest
@Override @Override
public void requestDestroyed(ServletRequestEvent sre) public void requestDestroyed(ServletRequestEvent sre)
{ {
Request.MultiPartInputStream m = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); Request.MultiParts m = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m); assertNotNull (m);
ContextHandler.Context c = m.getContext();
assertNotNull (c); assertNotNull (c);
assertTrue(c == sre.getServletContext()); assertTrue(c == sre.getServletContext());
assertTrue(!m.getParsedParts().isEmpty()); assertTrue(!m.isEmpty());
assertTrue(testTmpDir.list().length == 2); assertTrue(testTmpDir.list().length == 2);
super.requestDestroyed(sre); super.requestDestroyed(sre);
String[] files = testTmpDir.list(); String[] files = testTmpDir.list();
@ -401,7 +399,7 @@ public class RequestTest
multipart; multipart;
String responses=_connector.getResponse(request); String responses=_connector.getResponse(request);
// System.err.println(responses); //System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 200")); assertTrue(responses.startsWith("HTTP/1.1 200"));
} }
@ -426,9 +424,9 @@ public class RequestTest
@Override @Override
public void requestDestroyed(ServletRequestEvent sre) public void requestDestroyed(ServletRequestEvent sre)
{ {
Request.MultiPartInputStream m = (Request.MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM); Request.MultiParts m = (Request.MultiParts)sre.getServletRequest().getAttribute(Request.__MULTIPARTS);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m); assertNotNull (m);
ContextHandler.Context c = m.getContext();
assertNotNull (c); assertNotNull (c);
assertTrue(c == sre.getServletContext()); assertTrue(c == sre.getServletContext());
super.requestDestroyed(sre); super.requestDestroyed(sre);

View File

@ -78,15 +78,28 @@ public class MultiPartInputStreamParser
protected File _contextTmpDir; protected File _contextTmpDir;
protected boolean _deleteOnExit; protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames; protected boolean _writeFilesWithFilenames;
protected boolean _parsed;
private EnumSet<NonCompliance> nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class); private EnumSet<NonCompliance> nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class);
public enum NonCompliance public enum NonCompliance
{ {
CR_TERMINATION, CR_LINE_TERMINATION("https://tools.ietf.org/html/rfc2046#section-4.1.1"),
LF_TERMINATION, LF_LINE_TERMINATION("https://tools.ietf.org/html/rfc2046#section-4.1.1"),
NO_INITIAL_CRLF, NO_CRLF_AFTER_PREAMBLE("https://tools.ietf.org/html/rfc2046#section-5.1.1"),
BASE64_TRANSFER_ENCODING, BASE64_TRANSFER_ENCODING("https://tools.ietf.org/html/rfc7578#section-4.7"),
QUOTED_PRINTABLE_TRANSFER_ENCODING 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()) if (((ServletInputStream)in).isFinished())
{ {
_parts = EMPTY_MAP; _parts = EMPTY_MAP;
_parsed = true;
return; return;
} }
} }
@ -441,12 +455,12 @@ public class MultiPartInputStreamParser
/** /**
* Delete any tmp storage for parts, and clear out the parts list. * Delete any tmp storage for parts, and clear out the parts list.
*
* @throws MultiException if unable to delete the parts
*/ */
public void deleteParts () public void deleteParts ()
throws MultiException
{ {
if(!_parsed)
return;
Collection<Part> parts = getParsedParts(); Collection<Part> parts = getParsedParts();
MultiException err = new MultiException(); MultiException err = new MultiException();
for (Part p:parts) for (Part p:parts)
@ -462,7 +476,7 @@ public class MultiPartInputStreamParser
} }
_parts.clear(); _parts.clear();
err.ifExceptionThrowMulti(); err.ifExceptionThrowRuntime();
} }
@ -475,7 +489,8 @@ public class MultiPartInputStreamParser
public Collection<Part> getParts() public Collection<Part> getParts()
throws IOException throws IOException
{ {
parse(); if(!_parsed)
parse();
throwIfError(); throwIfError();
@ -500,7 +515,8 @@ public class MultiPartInputStreamParser
public Part getPart(String name) public Part getPart(String name)
throws IOException throws IOException
{ {
parse(); if(_parsed)
parse();
throwIfError(); throwIfError();
return _parts.getValue(name, 0); return _parts.getValue(name, 0);
} }
@ -530,8 +546,9 @@ public class MultiPartInputStreamParser
protected void parse () protected void parse ()
{ {
//have we already parsed the input? //have we already parsed the input?
if (_parts != null || _err != null) if (_parsed)
return; return;
_parsed = true;
//initialize //initialize
@ -616,7 +633,7 @@ public class MultiPartInputStreamParser
// check compliance of preamble // check compliance of preamble
if (Character.isWhitespace(untrimmed.charAt(0))) if (Character.isWhitespace(untrimmed.charAt(0)))
nonComplianceWarnings.add(NonCompliance.NO_INITIAL_CRLF); nonComplianceWarnings.add(NonCompliance.NO_CRLF_AFTER_PREAMBLE);
// Read each part // Read each part
boolean lastPart=false; boolean lastPart=false;
@ -847,9 +864,9 @@ public class MultiPartInputStreamParser
EnumSet<Termination> term = ((ReadLineInputStream)_in).getLineTerminations(); EnumSet<Termination> term = ((ReadLineInputStream)_in).getLineTerminations();
if(term.contains(Termination.CR)) if(term.contains(Termination.CR))
nonComplianceWarnings.add(NonCompliance.CR_TERMINATION); nonComplianceWarnings.add(NonCompliance.CR_LINE_TERMINATION);
if(term.contains(Termination.LF)) if(term.contains(Termination.LF))
nonComplianceWarnings.add(NonCompliance.LF_TERMINATION); nonComplianceWarnings.add(NonCompliance.LF_LINE_TERMINATION);
} }
else else
throw new IOException("Incomplete parts"); throw new IOException("Incomplete parts");

View File

@ -378,7 +378,7 @@ public class MultiPartInputStreamTest
IO.copy(stuff.getInputStream(), baos); IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("aaaa")); 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); IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("bbbbb")); 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); IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other")); 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 @Test
@ -671,7 +671,7 @@ public class MultiPartInputStreamTest
IO.copy(p2.getInputStream(), baos); IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other")); 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 @Test
@ -710,7 +710,7 @@ public class MultiPartInputStreamTest
IO.copy(p2.getInputStream(), baos); IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other")); 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 @Test