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:
parent
be2d6ebb29
commit
13b15e3566
|
@ -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<List<Part>> values = _parts.values();
|
||||
for (List<Part> partList : values)
|
||||
{
|
||||
if(partList.size() != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the already parsed parts.
|
||||
*
|
||||
* @return the parts that were parsed
|
||||
*/
|
||||
@Deprecated
|
||||
public Collection<Part> 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<Part> parts = getParsedParts();
|
||||
if (!_parsed)
|
||||
return;
|
||||
|
||||
Collection<Part> 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<Part> getParts() throws IOException
|
||||
{
|
||||
parse();
|
||||
if (!_parsed)
|
||||
parse();
|
||||
throwIfError();
|
||||
|
||||
Collection<List<Part>> 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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<Locale> __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<Part> getParts(MultiMap<String> 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(__MULTIPARTS, _multiParts);
|
||||
Collection<Part> 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;
|
||||
|
||||
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
|
||||
setAttribute(__MULTIPART_CONTEXT, _context);
|
||||
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
|
||||
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 Collection<Part> getParts();
|
||||
public Part getPart(String name);
|
||||
public boolean isEmpty();
|
||||
public ContextHandler.Context getContext();
|
||||
}
|
||||
|
||||
public MultiPartInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
|
||||
|
||||
public class MultiPartsHttpParser implements MultiParts
|
||||
{
|
||||
private final MultiPartFormInputStream _httpParser;
|
||||
private final ContextHandler.Context _context;
|
||||
|
||||
public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) throws IOException
|
||||
{
|
||||
this(in, contentType, config, contextTmpDir, false);
|
||||
_httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir);
|
||||
_context = Request.this._context;
|
||||
_httpParser.getParts();
|
||||
}
|
||||
|
||||
public MultiPartInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, boolean useOldParser)
|
||||
@Override
|
||||
public Collection<Part> getParts()
|
||||
{
|
||||
if(useOldParser)
|
||||
usingNewParser = false;
|
||||
else
|
||||
usingNewParser = true;
|
||||
|
||||
if(usingNewParser)
|
||||
_newParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir);
|
||||
else
|
||||
_oldParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir);
|
||||
try
|
||||
{
|
||||
return _httpParser.getParts();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Part> getParts() throws IOException {
|
||||
Collection<Part> parts = null;
|
||||
|
||||
if(usingNewParser)
|
||||
parts = _newParser.getParts();
|
||||
else
|
||||
parts = _oldParser.getParts();
|
||||
|
||||
return parts;
|
||||
@Override
|
||||
public Part getPart(String name) {
|
||||
try
|
||||
{
|
||||
return _httpParser.getPart(name);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
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 void close()
|
||||
{
|
||||
String contentDisposition = null;
|
||||
|
||||
if(usingNewParser)
|
||||
contentDisposition = ((MultiPartFormInputStream.MultiPart)p).getContentDispositionFilename();
|
||||
else
|
||||
contentDisposition = ((MultiPartInputStreamParser.MultiPart)p).getContentDispositionFilename();
|
||||
|
||||
return contentDisposition;
|
||||
_httpParser.deleteParts();
|
||||
}
|
||||
|
||||
public void deleteParts() throws MultiException
|
||||
@Override
|
||||
public boolean isEmpty()
|
||||
{
|
||||
if(usingNewParser)
|
||||
_newParser.deleteParts();
|
||||
else
|
||||
_oldParser.deleteParts();
|
||||
return _httpParser.isEmpty();
|
||||
}
|
||||
|
||||
public Collection<Part> getParsedParts()
|
||||
@Override
|
||||
public Context getContext()
|
||||
{
|
||||
Collection<Part> parsedParts = null;
|
||||
|
||||
if(usingNewParser)
|
||||
parsedParts = _newParser.getParsedParts();
|
||||
else
|
||||
parsedParts = _oldParser.getParsedParts();
|
||||
|
||||
return parsedParts;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, String[]> 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<String, String[]> 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);
|
||||
|
|
|
@ -78,15 +78,28 @@ public class MultiPartInputStreamParser
|
|||
protected File _contextTmpDir;
|
||||
protected boolean _deleteOnExit;
|
||||
protected boolean _writeFilesWithFilenames;
|
||||
protected boolean _parsed;
|
||||
|
||||
private EnumSet<NonCompliance> 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<Part> 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<Part> 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<Termination> 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");
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue