Merge pull request #2332 from eclipse/jetty-9.4.x-1027-Multipart

## SearchPattern
New class which does a fast search for patterns within strings and arrays of bytes using an implementation of the Boyer–Moore–Horspool algorithm. This was written to be used in the new MultiPartParser class to search for delimeter boundaries.

## MultiPartParser
New class which uses the SearchPattern to parse a MultiPart Mime given a ByteBuffer. Written in a non-blocking style so can be used asynchronously (although not currently be being used this way).

## MultiPartFormInputStream
New class which uses the MultiPartParser to parse a MultiPart Mime input stream into a Collection of Parts. This class is in org/eclipse/jetty/http and is designed to replace org/eclipse/jetty/util/MultiPartInputStreamParser.

## MultiPartInputStreamParser and Non Compliances
This class has been deprecated and replaced by org.eclipse.jetty.http.MultiPartFormInputStream. It accepts formats non compliant with the RFC that the new MultiPartFormInputStream does not accept. When this occurs violations are recorded by the method getNonComplianceWarnings().

## MultiParts
New interface to allow switching between the different implementations. This allows MultiParts to function in two different modes. The LEGACY implementation using the UTIL parser which may parse forms containing non compliances with the RFC, and the RFC7578 implementation using the new and faster HTTP parser. This file contains the implementations of MultiParts for HTTP and UTIL parsers as nested classes which are used by Request.

## Request
Changed to use the new MultiParts interface instead of the MultiPartInputStreamParser class. with a method called newMultiParts which will construct a MultiPart using one of the HTTP or UTIL implementations depending on what compliance mode is set.

## Jetty Test Webapp Dump Servlet
Code added to display parts while running the dump test webapp if MuliPart form is submitted.

## MultiPartBenchMark
JMH Benchmark of the HTTP multipart parser vs the UTIL multipart parser.

testLargeGenerated parses a 10MB file of random binary data.
testParser parses a series of small multipart forms captured by a browser.
```
# Run complete. Total time: 00:02:09

Benchmark                              (parserType)  Mode  Cnt  Score   Error  Units
MultiPartBenchmark.testLargeGenerated          UTIL  avgt   10  0.252 ± 0.025   s/op
MultiPartBenchmark.testLargeGenerated          HTTP  avgt   10  0.035 ± 0.004   s/op
MultiPartBenchmark.testParser                  UTIL  avgt   10  0.028 ± 0.005   s/op
MultiPartBenchmark.testParser                  HTTP  avgt   10  0.015 ± 0.006   s/op
```
This commit is contained in:
Greg Wilkins 2018-04-04 17:30:36 +10:00 committed by GitHub
commit c41b6b7aab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
151 changed files with 6087 additions and 105 deletions

3
.gitattributes vendored
View File

@ -5,4 +5,5 @@
*.java eol=lf
*.xml eol=lf
Jenkinsfile eol=lf
*.js eol=lf
*.js eol=lf
*.raw binary

View File

@ -23,11 +23,28 @@
<artifactId>jetty-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
@ -64,7 +81,49 @@
<onlyAnalyze>org.eclipse.jetty.http.*</onlyAnalyze>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${jmhjar.name}</finalName>
<shadeTestJar>true</shadeTestJar>
<artifactSet>
<includes>
<include>org.openjdk.jmh:jmh-core</include>
</includes>
</artifactSet>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>org.openjdk.jmh:jmh-core</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -49,7 +49,7 @@ import org.eclipse.jetty.util.log.Logger;
* be altered in code and will affect all usages of the mode.
*/
public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so that extra custom modes can be defined dynamically
{
{
/** A Legacy compliance mode to match jetty's behavior prior to RFC2616 and RFC7230. It only
* contains {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}
*/
@ -82,6 +82,8 @@ public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so t
@Deprecated
CUSTOM3(sectionsByProperty("CUSTOM3"));
public static final String VIOLATIONS_ATTR = "org.eclipse.jetty.http.compliance.violations";
private static final Logger LOG = Log.getLogger(HttpParser.class);
private static EnumSet<HttpComplianceSection> sectionsByProperty(String property)
{

View File

@ -58,6 +58,7 @@ public class MimeTypes
FORM_ENCODED("application/x-www-form-urlencoded"),
MESSAGE_HTTP("message/http"),
MULTIPART_BYTERANGES("multipart/byteranges"),
MULTIPART_FORM_DATA("multipart/form-data"),
TEXT_HTML("text/html"),
TEXT_PLAIN("text/plain"),

View File

@ -0,0 +1,856 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ByteArrayOutputStream2;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* MultiPartInputStream
*
* Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
*
* @see <a href="https://tools.ietf.org/html/rfc7578">https://tools.ietf.org/html/rfc7578</a>
*/
public class MultiPartFormInputStream
{
private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class);
private final int _bufferSize = 16 * 1024;
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap());
protected InputStream _in;
protected MultipartConfigElement _config;
protected String _contentType;
protected MultiMap<Part> _parts;
protected Throwable _err;
protected File _tmpDir;
protected File _contextTmpDir;
protected boolean _deleteOnExit;
protected boolean _writeFilesWithFilenames;
protected boolean _parsed;
public class MultiPart implements Part
{
protected String _name;
protected String _filename;
protected File _file;
protected OutputStream _out;
protected ByteArrayOutputStream2 _bout;
protected String _contentType;
protected MultiMap<String> _headers;
protected long _size = 0;
protected boolean _temporary = true;
public MultiPart(String name, String filename) throws IOException
{
_name = name;
_filename = filename;
}
@Override
public String toString()
{
return String.format("Part{n=%s,fn=%s,ct=%s,s=%d,tmp=%b,file=%s}",_name,_filename,_contentType,_size,_temporary,_file);
}
protected void setContentType(String contentType)
{
_contentType = contentType;
}
protected void open() throws IOException
{
// We will either be writing to a file, if it has a filename on the content-disposition
// and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
// will need to change to write to a file.
if (isWriteFilesWithFilenames() && _filename != null && _filename.trim().length() > 0)
{
createFile();
}
else
{
// Write to a buffer in memory until we discover we've exceed the
// MultipartConfig fileSizeThreshold
_out = _bout = new ByteArrayOutputStream2();
}
}
protected void close() throws IOException
{
_out.close();
}
protected void write(int b) throws IOException
{
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartFormInputStream.this._config.getFileSizeThreshold()
&& _file == null)
createFile();
_out.write(b);
_size++;
}
protected void write(byte[] bytes, int offset, int length) throws IOException
{
if (MultiPartFormInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartFormInputStream.this._config.getMaxFileSize())
throw new IllegalStateException("Multipart Mime part " + _name + " exceeds max filesize");
if (MultiPartFormInputStream.this._config.getFileSizeThreshold() > 0
&& _size + length > MultiPartFormInputStream.this._config.getFileSizeThreshold() && _file == null)
createFile();
_out.write(bytes,offset,length);
_size += length;
}
protected void createFile() throws IOException
{
/*
* Some statics just to make the code below easier to understand This get optimized away during the compile anyway
*/
final boolean USER = true;
final boolean WORLD = false;
_file = File.createTempFile("MultiPart","",MultiPartFormInputStream.this._tmpDir);
_file.setReadable(false,WORLD); // (reset) disable it for everyone first
_file.setReadable(true,USER); // enable for user only
if (_deleteOnExit)
_file.deleteOnExit();
FileOutputStream fos = new FileOutputStream(_file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
if (_size > 0 && _out != null)
{
// already written some bytes, so need to copy them into the file
_out.flush();
_bout.writeTo(bos);
_out.close();
}
_bout = null;
_out = bos;
}
protected void setHeaders(MultiMap<String> headers)
{
_headers = headers;
}
/**
* @see javax.servlet.http.Part#getContentType()
*/
@Override
public String getContentType()
{
return _contentType;
}
/**
* @see javax.servlet.http.Part#getHeader(java.lang.String)
*/
@Override
public String getHeader(String name)
{
if (name == null)
return null;
return _headers.getValue(StringUtil.asciiToLowerCase(name),0);
}
/**
* @see javax.servlet.http.Part#getHeaderNames()
*/
@Override
public Collection<String> getHeaderNames()
{
return _headers.keySet();
}
/**
* @see javax.servlet.http.Part#getHeaders(java.lang.String)
*/
@Override
public Collection<String> getHeaders(String name)
{
return _headers.getValues(name);
}
/**
* @see javax.servlet.http.Part#getInputStream()
*/
@Override
public InputStream getInputStream() throws IOException
{
if (_file != null)
{
// written to a file, whether temporary or not
return new BufferedInputStream(new FileInputStream(_file));
}
else
{
// part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
}
}
/**
* @see javax.servlet.http.Part#getSubmittedFileName()
*/
@Override
public String getSubmittedFileName()
{
return getContentDispositionFilename();
}
public byte[] getBytes()
{
if (_bout != null)
return _bout.toByteArray();
return null;
}
/**
* @see javax.servlet.http.Part#getName()
*/
@Override
public String getName()
{
return _name;
}
/**
* @see javax.servlet.http.Part#getSize()
*/
@Override
public long getSize()
{
return _size;
}
/**
* @see javax.servlet.http.Part#write(java.lang.String)
*/
@Override
public void write(String fileName) throws IOException
{
if (_file == null)
{
_temporary = false;
// part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File(_tmpDir,fileName);
BufferedOutputStream bos = null;
try
{
bos = new BufferedOutputStream(new FileOutputStream(_file));
_bout.writeTo(bos);
bos.flush();
}
finally
{
if (bos != null)
bos.close();
_bout = null;
}
}
else
{
// the part data is already written to a temporary file, just rename it
_temporary = false;
Path src = _file.toPath();
Path target = src.resolveSibling(fileName);
Files.move(src,target,StandardCopyOption.REPLACE_EXISTING);
_file = target.toFile();
}
}
/**
* Remove the file, whether or not Part.write() was called on it (ie no longer temporary)
*
* @see javax.servlet.http.Part#delete()
*/
@Override
public void delete() throws IOException
{
if (_file != null && _file.exists())
_file.delete();
}
/**
* Only remove tmp files.
*
* @throws IOException
* if unable to delete the file
*/
public void cleanUp() throws IOException
{
if (_temporary && _file != null && _file.exists())
_file.delete();
}
/**
* Get the file
*
* @return the file, if any, the data has been written to.
*/
public File getFile()
{
return _file;
}
/**
* Get the filename from the content-disposition.
*
* @return null or the filename
*/
public String getContentDispositionFilename()
{
return _filename;
}
}
/**
* @param in
* Request input stream
* @param contentType
* Content-Type header
* @param config
* MultipartConfigElement
* @param contextTmpDir
* javax.servlet.context.tempdir
*/
public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
{
_contentType = contentType;
_config = config;
_contextTmpDir = contextTmpDir;
if (_contextTmpDir == null)
_contextTmpDir = new File(System.getProperty("java.io.tmpdir"));
if (_config == null)
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
if (in instanceof ServletInputStream)
{
if (((ServletInputStream)in).isFinished())
{
_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)
return Collections.emptyList();
Collection<List<Part>> values = _parts.values();
List<Part> parts = new ArrayList<>();
for (List<Part> o : values)
{
List<Part> asList = LazyList.getList(o,false);
parts.addAll(asList);
}
return parts;
}
/**
* Delete any tmp storage for parts, and clear out the parts list.
*/
public void deleteParts()
{
if (!_parsed)
return;
Collection<Part> parts;
try
{
parts = getParts();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
MultiException err = new MultiException();
for (Part p : parts)
{
try
{
((MultiPart)p).cleanUp();
}
catch (Exception e)
{
err.add(e);
}
}
_parts.clear();
err.ifExceptionThrowRuntime();
}
/**
* Parse, if necessary, the multipart data and return the list of Parts.
*
* @return the parts
* @throws IOException
* if unable to get the parts
*/
public Collection<Part> getParts() throws IOException
{
if (!_parsed)
parse();
throwIfError();
Collection<List<Part>> values = _parts.values();
List<Part> parts = new ArrayList<>();
for (List<Part> o : values)
{
List<Part> asList = LazyList.getList(o,false);
parts.addAll(asList);
}
return parts;
}
/**
* Get the named Part.
*
* @param name
* the part name
* @return the parts
* @throws IOException
* if unable to get the part
*/
public Part getPart(String name) throws IOException
{
if(!_parsed)
parse();
throwIfError();
return _parts.getValue(name,0);
}
/**
* Throws an exception if one has been latched.
*
* @throws IOException
* the exception (if present)
*/
protected void throwIfError() throws IOException
{
if (_err != null)
{
_err.addSuppressed(new Throwable());
if (_err instanceof IOException)
throw (IOException)_err;
if (_err instanceof IllegalStateException)
throw (IllegalStateException)_err;
throw new IllegalStateException(_err);
}
}
/**
* Parse, if necessary, the multipart stream.
*
*/
protected void parse()
{
// have we already parsed the input?
if (_parsed)
return;
_parsed = true;
try
{
// initialize
_parts = new MultiMap<>();
// if its not a multipart request, don't parse it
if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
return;
// sort out the location to which to write the files
if (_config.getLocation() == null)
_tmpDir = _contextTmpDir;
else if ("".equals(_config.getLocation()))
_tmpDir = _contextTmpDir;
else
{
File f = new File(_config.getLocation());
if (f.isAbsolute())
_tmpDir = f;
else
_tmpDir = new File(_contextTmpDir,_config.getLocation());
}
if (!_tmpDir.exists())
_tmpDir.mkdirs();
String contentTypeBoundary = "";
int bstart = _contentType.indexOf("boundary=");
if (bstart >= 0)
{
int bend = _contentType.indexOf(";",bstart);
bend = (bend < 0?_contentType.length():bend);
contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
}
Handler handler = new Handler();
MultiPartParser parser = new MultiPartParser(handler,contentTypeBoundary);
// Create a buffer to store data from stream //
byte[] data = new byte[_bufferSize];
int len = 0;
/*
* keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
*/
long total = 0;
while (true)
{
len = _in.read(data);
if (len > 0)
{
total += len;
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
{
_err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")");
return;
}
ByteBuffer buffer = BufferUtil.toBuffer(data);
buffer.limit(len);
if (parser.parse(buffer,false))
break;
if(buffer.hasRemaining())
throw new IllegalStateException("Buffer did not fully consume");
}
else if (len == -1)
{
parser.parse(BufferUtil.EMPTY_BUFFER,true);
break;
}
}
// check for exceptions
if (_err != null)
{
return;
}
// check we read to the end of the message
if (parser.getState() != MultiPartParser.State.END)
{
if (parser.getState() == MultiPartParser.State.PREAMBLE)
_err = new IOException("Missing initial multi part boundary");
else
_err = new IOException("Incomplete Multipart");
}
if (LOG.isDebugEnabled())
{
LOG.debug("Parsing Complete {} err={}",parser,_err);
}
}
catch (Throwable e)
{
_err = e;
return;
}
}
class Handler implements MultiPartParser.Handler
{
private MultiPart _part = null;
private String contentDisposition = null;
private String contentType = null;
private MultiMap<String> headers = new MultiMap<>();
@Override
public boolean messageComplete()
{
return true;
}
@Override
public void parsedField(String key, String value)
{
// Add to headers and mark if one of these fields. //
headers.put(StringUtil.asciiToLowerCase(key),value);
if (key.equalsIgnoreCase("content-disposition"))
contentDisposition = value;
else if (key.equalsIgnoreCase("content-type"))
contentType = value;
// Transfer encoding is not longer considers as it is deprecated as per
// https://tools.ietf.org/html/rfc7578#section-4.7
}
@Override
public boolean headerComplete()
{
if(LOG.isDebugEnabled())
{
LOG.debug("headerComplete {}",this);
}
try
{
// Extract content-disposition
boolean form_data = false;
if (contentDisposition == null)
{
throw new IOException("Missing content-disposition");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentDisposition,";",false,true);
String name = null;
String filename = null;
while (tok.hasMoreTokens())
{
String t = tok.nextToken().trim();
String tl = StringUtil.asciiToLowerCase(t);
if (tl.startsWith("form-data"))
form_data = true;
else if (tl.startsWith("name="))
name = value(t);
else if (tl.startsWith("filename="))
filename = filenameValue(t);
}
// Check disposition
if (!form_data)
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)
throw new IOException("No name in part");
// create the new part
_part = new MultiPart(name,filename);
_part.setHeaders(headers);
_part.setContentType(contentType);
_parts.add(name,_part);
try
{
_part.open();
}
catch (IOException e)
{
_err = e;
return true;
}
}
catch (Exception e)
{
_err = e;
return true;
}
return false;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
if(_part == null)
return false;
if (BufferUtil.hasContent(buffer))
{
try
{
_part.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
}
catch (IOException e)
{
_err = e;
return true;
}
}
if (last)
{
try
{
_part.close();
}
catch (IOException e)
{
_err = e;
return true;
}
}
return false;
}
@Override
public void startPart()
{
reset();
}
@Override
public void earlyEOF()
{
if (LOG.isDebugEnabled())
LOG.debug("Early EOF {}",MultiPartFormInputStream.this);
}
public void reset()
{
_part = null;
contentDisposition = null;
contentType = null;
headers = new MultiMap<>();
}
}
public void setDeleteOnExit(boolean deleteOnExit)
{
_deleteOnExit = deleteOnExit;
}
public void setWriteFilesWithFilenames(boolean writeFilesWithFilenames)
{
_writeFilesWithFilenames = writeFilesWithFilenames;
}
public boolean isWriteFilesWithFilenames()
{
return _writeFilesWithFilenames;
}
public boolean isDeleteOnExit()
{
return _deleteOnExit;
}
/* ------------------------------------------------------------ */
private String value(String nameEqualsValue)
{
int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx + 1).trim();
return QuotedStringTokenizer.unquoteOnly(value);
}
/* ------------------------------------------------------------ */
private String filenameValue(String nameEqualsValue)
{
int idx = nameEqualsValue.indexOf('=');
String value = nameEqualsValue.substring(idx + 1).trim();
if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
{
// incorrectly escaped IE filenames that have the whole path
// we just strip any leading & trailing quotes and leave it as is
char first = value.charAt(0);
if (first == '"' || first == '\'')
value = value.substring(1);
char last = value.charAt(value.length() - 1);
if (last == '"' || last == '\'')
value = value.substring(0,value.length() - 1);
return value;
}
else
// unquote the string, but allow any backslashes that don't
// form a valid escape sequence to remain as many browsers
// even on *nix systems will not escape a filename containing
// backslashes
return QuotedStringTokenizer.unquoteOnly(value,true);
}
}

View File

@ -0,0 +1,766 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.EnumSet;
import org.eclipse.jetty.http.HttpParser.RequestHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.SearchPattern;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** A parser for MultiPart content type.
*
* @see <a href="https://tools.ietf.org/html/rfc2046#section-5.1">https://tools.ietf.org/html/rfc2046#section-5.1</a>
* @see <a href="https://tools.ietf.org/html/rfc2045">https://tools.ietf.org/html/rfc2045</a>
*/
public class MultiPartParser
{
public static final Logger LOG = Log.getLogger(MultiPartParser.class);
static final byte COLON = (byte)':';
static final byte TAB = 0x09;
static final byte LINE_FEED = 0x0A;
static final byte CARRIAGE_RETURN = 0x0D;
static final byte SPACE = 0x20;
static final byte[] CRLF =
{ CARRIAGE_RETURN, LINE_FEED };
static final byte SEMI_COLON = (byte)';';
// States
public enum FieldState
{
FIELD,
IN_NAME,
AFTER_NAME,
VALUE,
IN_VALUE
}
// States
public enum State
{
PREAMBLE,
DELIMITER,
DELIMITER_PADDING,
DELIMITER_CLOSE,
BODY_PART,
FIRST_OCTETS,
OCTETS,
EPILOGUE,
END
}
private final static EnumSet<State> __delimiterStates = EnumSet.of(State.DELIMITER,State.DELIMITER_CLOSE,State.DELIMITER_PADDING);
private final boolean DEBUG = LOG.isDebugEnabled();
private final Handler _handler;
private final SearchPattern _delimiterSearch;
private String _fieldName;
private String _fieldValue;
private State _state = State.PREAMBLE;
private FieldState _fieldState = FieldState.FIELD;
private int _partialBoundary = 2; // No CRLF if no preamble
private boolean _cr;
private ByteBuffer _patternBuffer;
private final Utf8StringBuilder _string = new Utf8StringBuilder();
private int _length;
private int _totalHeaderLineLength = -1;
private int _maxHeaderLineLength = 998;
/* ------------------------------------------------------------------------------- */
public MultiPartParser(Handler handler, String boundary)
{
_handler = handler;
String delimiter = "\r\n--" + boundary;
_patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
_delimiterSearch = SearchPattern.compile(_patternBuffer.array());
}
public void reset()
{
_state = State.PREAMBLE;
_fieldState = FieldState.FIELD;
_partialBoundary = 2; // No CRLF if no preamble
}
/* ------------------------------------------------------------------------------- */
public Handler getHandler()
{
return _handler;
}
/* ------------------------------------------------------------------------------- */
public State getState()
{
return _state;
}
/* ------------------------------------------------------------------------------- */
public boolean isState(State state)
{
return _state == state;
}
/* ------------------------------------------------------------------------------- */
enum CharState
{
ILLEGAL, CR, LF, LEGAL
}
private final static CharState[] __charState;
static
{
// token = 1*tchar
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
// ; any VCHAR, except delimiters
// quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
// qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
// obs-text = %x80-FF
// comment = "(" *( ctext / quoted-pair / comment ) ")"
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
__charState = new CharState[256];
Arrays.fill(__charState,CharState.ILLEGAL);
__charState[LINE_FEED] = CharState.LF;
__charState[CARRIAGE_RETURN] = CharState.CR;
__charState[TAB] = CharState.LEGAL;
__charState[SPACE] = CharState.LEGAL;
__charState['!'] = CharState.LEGAL;
__charState['#'] = CharState.LEGAL;
__charState['$'] = CharState.LEGAL;
__charState['%'] = CharState.LEGAL;
__charState['&'] = CharState.LEGAL;
__charState['\''] = CharState.LEGAL;
__charState['*'] = CharState.LEGAL;
__charState['+'] = CharState.LEGAL;
__charState['-'] = CharState.LEGAL;
__charState['.'] = CharState.LEGAL;
__charState['^'] = CharState.LEGAL;
__charState['_'] = CharState.LEGAL;
__charState['`'] = CharState.LEGAL;
__charState['|'] = CharState.LEGAL;
__charState['~'] = CharState.LEGAL;
__charState['"'] = CharState.LEGAL;
__charState['\\'] = CharState.LEGAL;
__charState['('] = CharState.LEGAL;
__charState[')'] = CharState.LEGAL;
Arrays.fill(__charState,0x21,0x27 + 1,CharState.LEGAL);
Arrays.fill(__charState,0x2A,0x5B + 1,CharState.LEGAL);
Arrays.fill(__charState,0x5D,0x7E + 1,CharState.LEGAL);
Arrays.fill(__charState,0x80,0xFF + 1,CharState.LEGAL);
}
/* ------------------------------------------------------------------------------- */
private boolean hasNextByte(ByteBuffer buffer)
{
return BufferUtil.hasContent(buffer);
}
/* ------------------------------------------------------------------------------- */
private byte getNextByte(ByteBuffer buffer)
{
byte ch = buffer.get();
CharState s = __charState[0xff & ch];
switch (s)
{
case LF:
_cr = false;
return ch;
case CR:
if (_cr)
throw new BadMessageException("Bad EOL");
_cr = true;
if (buffer.hasRemaining())
return getNextByte(buffer);
// Can return 0 here to indicate the need for more characters,
// because a real 0 in the buffer would cause a BadMessage below
return 0;
case LEGAL:
if (_cr)
throw new BadMessageException("Bad EOL");
return ch;
case ILLEGAL:
default:
throw new IllegalCharacterException(_state,ch,buffer);
}
}
/* ------------------------------------------------------------------------------- */
private void setString(String s)
{
_string.reset();
_string.append(s);
_length = s.length();
}
/* ------------------------------------------------------------------------------- */
/*
* Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1
*/
private String takeString()
{
String s = _string.toString();
// trim trailing whitespace.
if (s.length()>_length)
s = s.substring(0,_length);
_string.reset();
_length = -1;
return s;
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
*
* @param buffer the buffer to parse
* @param last whether this buffer contains last bit of content
* @return True if an {@link RequestHandler} method was called and it returned true;
*/
public boolean parse(ByteBuffer buffer, boolean last)
{
boolean handle = false;
while (handle == false && BufferUtil.hasContent(buffer))
{
switch (_state)
{
case PREAMBLE:
parsePreamble(buffer);
continue;
case DELIMITER:
case DELIMITER_PADDING:
case DELIMITER_CLOSE:
parseDelimiter(buffer);
continue;
case BODY_PART:
handle = parseMimePartHeaders(buffer);
break;
case FIRST_OCTETS:
case OCTETS:
handle = parseOctetContent(buffer);
break;
case EPILOGUE:
BufferUtil.clear(buffer);
break;
case END:
handle = true;
break;
default:
throw new IllegalStateException();
}
}
if (last && BufferUtil.isEmpty(buffer))
{
if (_state == State.EPILOGUE)
{
_state = State.END;
if(LOG.isDebugEnabled())
LOG.debug("messageComplete {}", this);
return _handler.messageComplete();
}
else
{
if(LOG.isDebugEnabled())
LOG.debug("earlyEOF {}", this);
_handler.earlyEOF();
return true;
}
}
return handle;
}
/* ------------------------------------------------------------------------------- */
private void parsePreamble(ByteBuffer buffer)
{
if (_partialBoundary > 0)
{
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary);
if (partial > 0)
{
if (partial == _delimiterSearch.getLength())
{
buffer.position(buffer.position() + partial - _partialBoundary);
_partialBoundary = 0;
setState(State.DELIMITER);
return;
}
_partialBoundary = partial;
BufferUtil.clear(buffer);
return;
}
_partialBoundary = 0;
}
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (delimiter >= 0)
{
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER);
return;
}
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
BufferUtil.clear(buffer);
return;
}
/* ------------------------------------------------------------------------------- */
private void parseDelimiter(ByteBuffer buffer)
{
while (__delimiterStates.contains(_state) && hasNextByte(buffer))
{
byte b = getNextByte(buffer);
if (b == 0)
return;
if (b == '\n')
{
setState(State.BODY_PART);
if(LOG.isDebugEnabled())
LOG.debug("startPart {}",this);
_handler.startPart();
return;
}
switch (_state)
{
case DELIMITER:
if (b == '-')
setState(State.DELIMITER_CLOSE);
else
setState(State.DELIMITER_PADDING);
continue;
case DELIMITER_CLOSE:
if (b == '-')
{
setState(State.EPILOGUE);
return;
}
setState(State.DELIMITER_PADDING);
continue;
case DELIMITER_PADDING:
default:
continue;
}
}
}
/* ------------------------------------------------------------------------------- */
/*
* Parse the message headers and return true if the handler has signaled for a return
*/
protected boolean parseMimePartHeaders(ByteBuffer buffer)
{
// Process headers
while (_state == State.BODY_PART && hasNextByte(buffer))
{
// process each character
byte b = getNextByte(buffer);
if (b == 0)
break;
if (b != LINE_FEED)
_totalHeaderLineLength++;
if (_totalHeaderLineLength > _maxHeaderLineLength)
throw new IllegalStateException("Header Line Exceeded Max Length");
switch (_fieldState)
{
case FIELD:
switch (b)
{
case SPACE:
case TAB:
{
// Folded field value!
if (_fieldName == null)
throw new IllegalStateException("First field folded");
if (_fieldValue == null)
{
_string.reset();
_length = 0;
}
else
{
setString(_fieldValue);
_string.append(' ');
_length++;
_fieldValue = null;
}
setState(FieldState.VALUE);
break;
}
case LINE_FEED:
{
handleField();
setState(State.FIRST_OCTETS);
_partialBoundary = 2; // CRLF is option for empty parts
if(LOG.isDebugEnabled())
LOG.debug("headerComplete {}", this);
if (_handler.headerComplete())
return true;
break;
}
default:
{
// process previous header
handleField();
// New header
setState(FieldState.IN_NAME);
_string.reset();
_string.append(b);
_length = 1;
}
}
break;
case IN_NAME:
switch (b)
{
case COLON:
_fieldName = takeString();
_length = -1;
setState(FieldState.VALUE);
break;
case SPACE:
// Ignore trailing whitespaces
setState(FieldState.AFTER_NAME);
break;
case LINE_FEED:
{
if(LOG.isDebugEnabled())
LOG.debug("Line Feed in Name {}", this);
handleField();
setState(FieldState.FIELD);
break;
}
default:
_string.append(b);
_length = _string.length();
break;
}
break;
case AFTER_NAME:
switch (b)
{
case COLON:
_fieldName = takeString();
_length = -1;
setState(FieldState.VALUE);
break;
case LINE_FEED:
_fieldName = takeString();
_string.reset();
_fieldValue = "";
_length = -1;
break;
case SPACE:
break;
default:
throw new IllegalCharacterException(_state,b,buffer);
}
break;
case VALUE:
switch (b)
{
case LINE_FEED:
_string.reset();
_fieldValue = "";
_length = -1;
setState(FieldState.FIELD);
break;
case SPACE:
case TAB:
break;
default:
_string.append(b);
_length = _string.length();
setState(FieldState.IN_VALUE);
break;
}
break;
case IN_VALUE:
switch (b)
{
case SPACE:
_string.append(b);
break;
case LINE_FEED:
if (_length > 0)
{
_fieldValue = takeString();
_length = -1;
_totalHeaderLineLength = -1;
}
setState(FieldState.FIELD);
break;
default:
_string.append(b);
if (b > SPACE || b < 0)
_length = _string.length();
break;
}
break;
default:
throw new IllegalStateException(_state.toString());
}
}
return false;
}
/* ------------------------------------------------------------------------------- */
private void handleField()
{
if(LOG.isDebugEnabled())
LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this);
if (_fieldName != null && _fieldValue != null)
_handler.parsedField(_fieldName,_fieldValue);
_fieldName = _fieldValue = null;
}
/* ------------------------------------------------------------------------------- */
protected boolean parseOctetContent(ByteBuffer buffer)
{
// Starts With
if (_partialBoundary > 0)
{
int partial = _delimiterSearch.startsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining(),_partialBoundary);
if (partial > 0)
{
if (partial == _delimiterSearch.getLength())
{
buffer.position(buffer.position() + _delimiterSearch.getLength() - _partialBoundary);
setState(State.DELIMITER);
_partialBoundary = 0;
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER),true,this);
return _handler.content(BufferUtil.EMPTY_BUFFER,true);
}
_partialBoundary = partial;
BufferUtil.clear(buffer);
return false;
}
else
{
// output up to _partialBoundary of the search pattern
ByteBuffer content = _patternBuffer.slice();
if (_state == State.FIRST_OCTETS)
{
setState(State.OCTETS);
content.position(2);
}
content.limit(_partialBoundary);
_partialBoundary = 0;
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
if (_handler.content(content,false))
return true;
}
}
// Contains
int delimiter = _delimiterSearch.match(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (delimiter >= 0)
{
ByteBuffer content = buffer.slice();
content.limit(delimiter - buffer.arrayOffset() - buffer.position());
buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
setState(State.DELIMITER);
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),true,this);
return _handler.content(content,true);
}
// Ends With
_partialBoundary = _delimiterSearch.endsWith(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
if (_partialBoundary > 0)
{
ByteBuffer content = buffer.slice();
content.limit(content.limit() - _partialBoundary);
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
BufferUtil.clear(buffer);
return _handler.content(content,false);
}
// There is normal content with no delimiter
ByteBuffer content = buffer.slice();
if(LOG.isDebugEnabled())
LOG.debug("Content={}, Last={} {}",BufferUtil.toDetailString(content),false,this);
BufferUtil.clear(buffer);
return _handler.content(content,false);
}
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
if (DEBUG)
LOG.debug("{} --> {}",_state,state);
_state = state;
}
/* ------------------------------------------------------------------------------- */
private void setState(FieldState state)
{
if (DEBUG)
LOG.debug("{}:{} --> {}",_state,_fieldState,state);
_fieldState = state;
}
/* ------------------------------------------------------------------------------- */
@Override
public String toString()
{
return String.format("%s{s=%s}",getClass().getSimpleName(),_state);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/*
* Event Handler interface These methods return true if the caller should process the events so far received (eg return from parseNext and call
* HttpChannel.handle). If multiple callbacks are called in sequence (eg headerComplete then messageComplete) from the same point in the parsing then it is
* sufficient for the caller to process the events only once.
*/
public interface Handler
{
public default void startPart()
{
}
public default void parsedField(String name, String value)
{
}
public default boolean headerComplete()
{
return false;
}
public default boolean content(ByteBuffer item, boolean last)
{
return false;
}
public default boolean messageComplete()
{
return false;
}
public default void earlyEOF()
{
}
}
/* ------------------------------------------------------------------------------- */
@SuppressWarnings("serial")
private static class IllegalCharacterException extends IllegalArgumentException
{
private IllegalCharacterException(State state, byte ch, ByteBuffer buffer)
{
super(String.format("Illegal character 0x%X",ch));
// Bug #460642 - don't reveal buffers to end user
LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
}
}
}

View File

@ -0,0 +1,404 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.Part;
import org.eclipse.jetty.toolchain.test.Hex;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class MultiPartCaptureTest
{
public static final int MAX_FILE_SIZE = 2 * 1024 * 1024;
public static final int MAX_REQUEST_SIZE = MAX_FILE_SIZE + (60 * 1024);
public static final int FILE_SIZE_THRESHOLD = 50;
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data()
{
List<Object[]> ret = new ArrayList<>();
// == Arbitrary / Non-Standard Examples ==
ret.add(new String[]{"multipart-uppercase"});
// ret.add(new String[]{"multipart-base64"}); // base64 transfer encoding deprecated
// ret.add(new String[]{"multipart-base64-long"}); // base64 transfer encoding deprecated
// == Capture of raw request body contents from Apache HttpClient 4.5.5 ==
ret.add(new String[]{"browser-capture-company-urlencoded-apache-httpcomp"});
ret.add(new String[]{"browser-capture-complex-apache-httpcomp"});
ret.add(new String[]{"browser-capture-duplicate-names-apache-httpcomp"});
ret.add(new String[]{"browser-capture-encoding-mess-apache-httpcomp"});
ret.add(new String[]{"browser-capture-nested-apache-httpcomp"});
ret.add(new String[]{"browser-capture-nested-binary-apache-httpcomp"});
ret.add(new String[]{"browser-capture-number-only2-apache-httpcomp"});
ret.add(new String[]{"browser-capture-number-only-apache-httpcomp"});
ret.add(new String[]{"browser-capture-sjis-apache-httpcomp"});
ret.add(new String[]{"browser-capture-strange-quoting-apache-httpcomp"});
ret.add(new String[]{"browser-capture-text-files-apache-httpcomp"});
ret.add(new String[]{"browser-capture-unicode-names-apache-httpcomp"});
ret.add(new String[]{"browser-capture-zalgo-text-plain-apache-httpcomp"});
// == Capture of raw request body contents from Eclipse Jetty Http Client 9.4.9 ==
ret.add(new String[]{"browser-capture-complex-jetty-client"});
ret.add(new String[]{"browser-capture-duplicate-names-jetty-client"});
ret.add(new String[]{"browser-capture-encoding-mess-jetty-client"});
ret.add(new String[]{"browser-capture-nested-jetty-client"});
ret.add(new String[]{"browser-capture-number-only-jetty-client"});
ret.add(new String[]{"browser-capture-sjis-jetty-client"});
ret.add(new String[]{"browser-capture-text-files-jetty-client"});
ret.add(new String[]{"browser-capture-unicode-names-jetty-client"});
ret.add(new String[]{"browser-capture-whitespace-only-jetty-client"});
// == Capture of raw request body contents from various browsers ==
// simple form - 2 fields
ret.add(new String[]{"browser-capture-form1-android-chrome"});
ret.add(new String[]{"browser-capture-form1-android-firefox"});
ret.add(new String[]{"browser-capture-form1-chrome"});
ret.add(new String[]{"browser-capture-form1-edge"});
ret.add(new String[]{"browser-capture-form1-firefox"});
ret.add(new String[]{"browser-capture-form1-ios-safari"});
ret.add(new String[]{"browser-capture-form1-msie"});
ret.add(new String[]{"browser-capture-form1-osx-safari"});
// form submitted as shift-jis
ret.add(new String[]{"browser-capture-sjis-form-edge"});
ret.add(new String[]{"browser-capture-sjis-form-msie"});
// TODO: these might be addressable via Issue #2398
// ret.add(new String[]{"browser-capture-sjis-form-android-chrome"}); // contains html encoded character and unspecified charset defaults to utf-8
// ret.add(new String[]{"browser-capture-sjis-form-android-firefox"}); // contains html encoded character and unspecified charset defaults to utf-8
// ret.add(new String[]{"browser-capture-sjis-form-chrome"}); // contains html encoded character and unspecified charset defaults to utf-8
// ret.add(new String[]{"browser-capture-sjis-form-firefox"}); // contains html encoded character and unspecified charset defaults to utf-8
// ret.add(new String[]{"browser-capture-sjis-form-ios-safari"}); // contains html encoded character and unspecified charset defaults to utf-8
// ret.add(new String[]{"browser-capture-sjis-form-safari"}); // contains html encoded character and unspecified charset defaults to utf-8
// form submitted as shift-jis (with HTML5 specific hidden _charset_ field)
ret.add(new String[]{"browser-capture-sjis-charset-form-android-chrome"}); // contains html encoded character
ret.add(new String[]{"browser-capture-sjis-charset-form-android-firefox"}); // contains html encoded character
ret.add(new String[]{"browser-capture-sjis-charset-form-chrome"}); // contains html encoded character
ret.add(new String[]{"browser-capture-sjis-charset-form-edge"});
ret.add(new String[]{"browser-capture-sjis-charset-form-firefox"}); // contains html encoded character
ret.add(new String[]{"browser-capture-sjis-charset-form-ios-safari"}); // contains html encoded character
ret.add(new String[]{"browser-capture-sjis-charset-form-msie"});
ret.add(new String[]{"browser-capture-sjis-charset-form-safari"}); // contains html encoded character
// form submitted with simple file upload
ret.add(new String[]{"browser-capture-form-fileupload-android-chrome"});
ret.add(new String[]{"browser-capture-form-fileupload-android-firefox"});
ret.add(new String[]{"browser-capture-form-fileupload-chrome"});
ret.add(new String[]{"browser-capture-form-fileupload-edge"});
ret.add(new String[]{"browser-capture-form-fileupload-firefox"});
ret.add(new String[]{"browser-capture-form-fileupload-ios-safari"});
ret.add(new String[]{"browser-capture-form-fileupload-msie"});
ret.add(new String[]{"browser-capture-form-fileupload-safari"});
// form submitted with 2 files (1 binary, 1 text) and 2 text fields
ret.add(new String[]{"browser-capture-form-fileupload-alt-chrome"});
ret.add(new String[]{"browser-capture-form-fileupload-alt-edge"});
ret.add(new String[]{"browser-capture-form-fileupload-alt-firefox"});
ret.add(new String[]{"browser-capture-form-fileupload-alt-msie"});
ret.add(new String[]{"browser-capture-form-fileupload-alt-safari"});
return ret;
}
@Rule
public TestingDir testingDir = new TestingDir();
private final Path multipartRawFile;
private final MultipartExpectations multipartExpectations;
public MultiPartCaptureTest(String rawPrefix) throws IOException
{
multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + rawPrefix + ".raw");
Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + rawPrefix + ".expected.txt");
multipartExpectations = new MultipartExpectations(expectationPath);
}
@Test
public void testUtilParse() throws Exception
{
Path outputDir = testingDir.getEmptyPathDir();
MultipartConfigElement config = newMultipartConfigElement(outputDir);
try (InputStream in = Files.newInputStream(multipartRawFile))
{
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in,multipartExpectations.contentType,config,outputDir.toFile());
checkParts(parser.getParts(),s->
{
try
{
return parser.getPart(s);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
});
}
}
@Test
public void testHttpParse() throws Exception
{
Path outputDir = testingDir.getEmptyPathDir();
MultipartConfigElement config = newMultipartConfigElement(outputDir);
try (InputStream in = Files.newInputStream(multipartRawFile))
{
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, multipartExpectations.contentType, config, outputDir.toFile());
checkParts(parser.getParts(),s->
{
try
{
return parser.getPart(s);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
});
}
}
private void checkParts(Collection<Part> parts, Function<String, Part> getPart) throws Exception
{
// Evaluate Count
if (multipartExpectations.partCount >= 0)
{
assertThat("Mulitpart.parts.size", parts.size(), is(multipartExpectations.partCount));
}
String defaultCharset = UTF_8.toString();
Part charSetPart = getPart.apply("_charset_");
if(charSetPart != null)
{
defaultCharset = IO.toString(charSetPart.getInputStream());
}
// Evaluate expected Contents
for (NameValue expected : multipartExpectations.partContainsContents)
{
Part part = getPart.apply(expected.name);
assertThat("Part[" + expected.name + "]", part, is(notNullValue()));
try (InputStream partInputStream = part.getInputStream())
{
String charset = getCharsetFromContentType(part.getContentType(), defaultCharset);
String contents = IO.toString(partInputStream, charset);
assertThat("Part[" + expected.name + "].contents", contents, containsString(expected.value));
}
}
// Evaluate expected filenames
for (NameValue expected : multipartExpectations.partFilenames)
{
Part part = getPart.apply(expected.name);
assertThat("Part[" + expected.name + "]", part, is(notNullValue()));
assertThat("Part[" + expected.name + "]", part.getSubmittedFileName(), is(expected.value));
}
// Evaluate expected contents checksums
for (NameValue expected : multipartExpectations.partSha1sums)
{
Part part = getPart.apply(expected.name);
assertThat("Part[" + expected.name + "]", part, is(notNullValue()));
MessageDigest digest = MessageDigest.getInstance("SHA1");
try (InputStream partInputStream = part.getInputStream();
NoOpOutputStream noop = new NoOpOutputStream();
DigestOutputStream digester = new DigestOutputStream(noop, digest))
{
IO.copy(partInputStream, digester);
String actualSha1sum = Hex.asHex(digest.digest()).toLowerCase(Locale.US);
assertThat("Part[" + expected.name + "].sha1sum", actualSha1sum, Matchers.equalToIgnoringCase(expected.value));
}
}
}
private MultipartConfigElement newMultipartConfigElement(Path path)
{
return new MultipartConfigElement(path.toString(), MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD);
}
private String getCharsetFromContentType(String contentType, String defaultCharset)
{
if(StringUtil.isBlank(contentType))
{
return defaultCharset;
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(contentType, ";", false, false);
while(tok.hasMoreTokens())
{
String str = tok.nextToken().trim();
if(str.startsWith("charset="))
{
return str.substring("charset=".length());
}
}
return defaultCharset;
}
public static class NameValue
{
public String name;
public String value;
}
public static class MultipartExpectations
{
public final String contentType;
public final int partCount;
public final List<NameValue> partFilenames = new ArrayList<>();
public final List<NameValue> partSha1sums = new ArrayList<>();
public final List<NameValue> partContainsContents = new ArrayList<>();
public MultipartExpectations(Path expectationsPath) throws IOException
{
String parsedContentType = null;
String parsedPartCount = "-1";
try (BufferedReader reader = Files.newBufferedReader(expectationsPath))
{
String line;
while ((line = reader.readLine()) != null)
{
line = line.trim();
if (StringUtil.isBlank(line) || line.startsWith("#"))
{
// skip blanks and comments
continue;
}
String split[] = line.split("\\|");
switch (split[0])
{
case "Request-Header":
if(split[1].equalsIgnoreCase("Content-Type"))
{
parsedContentType = split[2];
}
break;
case "Content-Type":
parsedContentType = split[1];
break;
case "Parts-Count":
parsedPartCount = split[1];
break;
case "Part-ContainsContents":
{
NameValue pair = new NameValue();
pair.name = split[1];
pair.value = split[2];
partContainsContents.add(pair);
break;
}
case "Part-Filename":
{
NameValue pair = new NameValue();
pair.name = split[1];
pair.value = split[2];
partFilenames.add(pair);
break;
}
case "Part-Sha1sum":
{
NameValue pair = new NameValue();
pair.name = split[1];
pair.value = split[2];
partSha1sums.add(pair);
break;
}
default:
throw new IOException("Bad Line in " + expectationsPath + ": " + line);
}
}
}
Objects.requireNonNull(parsedContentType, "Missing required 'Content-Type' declaration: " + expectationsPath);
this.contentType = parsedContentType;
this.partCount = Integer.parseInt(parsedPartCount);
}
}
class NoOpOutputStream extends OutputStream
{
@Override
public void write(byte[] b) throws IOException
{
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
}
@Override
public void flush() throws IOException
{
}
@Override
public void close() throws IOException
{
}
@Override
public void write(int b) throws IOException
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,711 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.is;
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.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.Test;
public class MultiPartParserTest
{
@Test
public void testEmptyPreamble()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
}
@Test
public void testNoPreamble()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY \r\n");
parser.parse(data,false);
assertTrue(parser.isState(State.BODY_PART));
assertThat(data.remaining(),is(0));
}
@Test
public void testPreamble()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data;
data = BufferUtil.toBuffer("This is not part of a part\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("More data that almost includes \n--BOUNDARY but no CR before.");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("Could be a boundary \r\n--BOUNDAR");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("but not it isn't \r\n--BOUN");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("DARX nor is this");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
}
@Test
public void testPreambleCompleteBoundary()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data;
data = BufferUtil.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.BODY_PART));
assertThat(data.remaining(),is(0));
}
@Test
public void testPreambleSplitBoundary()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data;
data = BufferUtil.toBuffer("This is not part of a part\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("-");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("-");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("B");
parser.parse(data,false);
assertThat(parser.getState(),is(State.PREAMBLE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("OUNDARY-");
parser.parse(data,false);
assertThat(parser.getState(),is(State.DELIMITER_CLOSE));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("ignore\r");
parser.parse(data,false);
assertThat(parser.getState(),is(State.DELIMITER_PADDING));
assertThat(data.remaining(),is(0));
data = BufferUtil.toBuffer("\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.BODY_PART));
assertThat(data.remaining(),is(0));
}
@Test
public void testFirstPartNoFields()
{
MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler(){},"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.FIRST_OCTETS));
assertThat(data.remaining(),is(0));
}
@Test
public void testFirstPartFields()
{
TestHandler handler = new TestHandler()
{
@Override
public boolean headerComplete()
{
super.headerComplete();
return true;
}
};
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name0: value0\r\n"
+ "name1 :value1 \r\n"
+ "name2:value\r\n"
+ " 2\r\n"
+ "\r\n"
+ "Content");
parser.parse(data,false);
assertThat(parser.getState(),is(State.FIRST_OCTETS));
assertThat(data.remaining(),is(7));
assertThat(handler.fields,Matchers.contains("name0: value0","name1: value1", "name2: value 2", "<<COMPLETE>>"));
}
@Test
public void testFirstPartNoContent()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n"
+ "\r\n"
+ "\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("<<LAST>>"));
}
@Test
public void testFirstPartNoContentNoCRLF()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n"
+ "\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("<<LAST>>"));
}
@Test
public void testFirstPartContentLookingLikeNoCRLF()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\r\n"
+ "\r\n"
+ "-");
parser.parse(data,false);
data = BufferUtil.toBuffer("Content!");
parser.parse(data,false);
assertThat(parser.getState(), is(State.OCTETS));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("-","Content!"));
}
@Test
public void testFirstPartPartialContent()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n"
+ "\r\n"
+ "Hello\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.OCTETS));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello"));
data = BufferUtil.toBuffer(
"Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n"
+ "this is not a --BOUNDARY\r\n");
parser.parse(data,false);
assertThat(parser.getState(),is(State.OCTETS));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","\r\n","Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n"
+ "this is not a --BOUNDARY"));
}
@Test
public void testFirstPartShortContent()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n"
+ "\r\n"
+ "Hello\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>"));
}
@Test
public void testFirstPartLongContent()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
data = BufferUtil.toBuffer("--BOUNDARY\r\n"
+ "name: value\n"
+ "\r\n"
+ "Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n"
+ "\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Now is the time for all good ment to come to the aid of the party.\r\n"
+ "How now brown cow.\r\n"
+ "The quick brown fox jumped over the lazy dog.\r\n","<<LAST>>"));
}
@Test
public void testFirstPartLongContentNoCarriageReturn()
{
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer("");
//boundary still requires carriage return
data = BufferUtil.toBuffer("--BOUNDARY\n"
+ "name: value\n"
+ "\n"
+ "Now is the time for all good men to come to the aid of the party.\n"
+ "How now brown cow.\n"
+ "The quick brown fox jumped over the lazy dog.\n"
+ "\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(0));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Now is the time for all good men to come to the aid of the party.\n"
+ "How now brown cow.\n"
+ "The quick brown fox jumped over the lazy dog.\n","<<LAST>>"));
}
@Test
public void testBinaryPart()
{
byte[] random = new byte[8192];
final ByteBuffer bytes = BufferUtil.allocate(random.length);
ThreadLocalRandom.current().nextBytes(random);
// Arrays.fill(random,(byte)'X');
TestHandler handler = new TestHandler()
{
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
BufferUtil.append(bytes,buffer);
return last;
}
};
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n";
String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n";
ByteBuffer data = BufferUtil.allocate(preamble.length()+random.length+epilogue.length());
BufferUtil.append(data,BufferUtil.toBuffer(preamble));
BufferUtil.append(data,ByteBuffer.wrap(random));
BufferUtil.append(data,BufferUtil.toBuffer(epilogue));
parser.parse(data,true);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(data.remaining(),is(19));
assertThat(bytes.array(),is(random));
}
@Test
public void testEpilogue() {
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""
+ "--BOUNDARY\r\n"
+ "name: value\n"
+ "\r\n"
+ "Hello\r\n"
+ "--BOUNDARY--"
+ "epilogue here:"
+ "\r\n"
+ "--BOUNDARY--"
+ "\r\n"
+ "--BOUNDARY");
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields,Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content,Matchers.contains("Hello","<<LAST>>"));
parser.parse(data,true);
assertThat(parser.getState(), is(State.END));
}
@Test
public void testMultipleContent() {
TestHandler handler = new TestHandler();
MultiPartParser parser = new MultiPartParser(handler,"BOUNDARY");
ByteBuffer data = BufferUtil.toBuffer(""
+ "--BOUNDARY\r\n"
+ "name: value\n"
+ "\r\n"
+ "Hello"
+ "\r\n"
+ "--BOUNDARY\r\n"
+ "powerLevel: 9001\n"
+ "\r\n"
+ "secondary"
+ "\r\n"
+ "content"
+ "\r\n--BOUNDARY--"
+ "epilogue here");
/* Test First Content Section */
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>"));
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>"));
/* Test Second Content Section */
parser.parse(data,false);
assertThat(parser.getState(), is(State.DELIMITER));
assertThat(handler.fields, Matchers.contains("name: value", "<<COMPLETE>>","powerLevel: 9001","<<COMPLETE>>"));
assertThat(handler.content, Matchers.contains("Hello","<<LAST>>","secondary\r\ncontent","<<LAST>>"));
/* Test Progression to END State */
parser.parse(data,true);
assertThat(parser.getState(), is(State.END));
assertThat(data.remaining(),is(0));
}
@Test
public void testCrAsLineTermination() {
TestHandler handler = new TestHandler()
{
@Override public boolean messageComplete(){ return true; }
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
super.content(buffer,last);
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"AaB03x");
ByteBuffer data = BufferUtil.toBuffer(
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r"+
"Joe Blow\r\n"+
"--AaB03x--\r\n");
try
{
parser.parse(data,true);
fail("Invalid End of Line");
}
catch(BadMessageException e) {
assertTrue(e.getMessage().contains("Bad EOL"));
}
}
@Test
public void splitTest()
{
TestHandler handler = new TestHandler()
{
@Override
public boolean messageComplete()
{
return true;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
super.content(buffer,last);
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"---------------------------9051914041544843365972754266");
ByteBuffer data = BufferUtil.toBuffer(""+
"POST / HTTP/1.1\n" +
"Host: localhost:8000\n" +
"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" +
"Accept-Language: en-US,en;q=0.5\n" +
"Accept-Encoding: gzip, deflate\n" +
"Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET\n" +
"Connection: keep-alive\n" +
"Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266\n" +
"Content-Length: 554\n" +
"\r\n" +
"-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"text\"\n" +
"\n" +
"text default\r\n" +
"-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n" +
"Content-Type: text/plain\n" +
"\n" +
"Content of a.txt.\n" +
"\r\n" +
"-----------------------------9051914041544843365972754266\n" +
"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n" +
"Content-Type: text/html\n" +
"\n" +
"<!DOCTYPE html><title>Content of a.html.</title>\n" +
"\r\n" +
"-----------------------------9051914041544843365972754266\n" +
"Field1: value1\n" +
"Field2: value2\n" +
"Field3: value3\n" +
"Field4: value4\n" +
"Field5: value5\n" +
"Field6: value6\n" +
"Field7: value7\n" +
"Field8: value8\n" +
"Field9: value\n" +
" 9\n" +
"\r\n" +
"-----------------------------9051914041544843365972754266\n" +
"Field1: value1\n" +
"\r\n"+
"But the amount of denudation which the strata have\n" +
"in many places suffered, independently of the rate\n" +
"of accumulation of the degraded matter, probably\n" +
"offers the best evidence of the lapse of time. I remember\n" +
"having been much struck with the evidence of\n" +
"denudation, when viewing volcanic islands, which\n" +
"have been worn by the waves and pared all round\n" +
"into perpendicular cliffs of one or two thousand feet\n" +
"in height; for the gentle slope of the lava-streams,\n" +
"due to their formerly liquid state, showed at a glance\n" +
"how far the hard, rocky beds had once extended into\n" +
"the open ocean.\n" +
"\r\n" +
"-----------------------------9051914041544843365972754266--" +
"===== ajlkfja;lkdj;lakjd;lkjf ==== epilogue here ==== kajflajdfl;kjafl;kjl;dkfja ====\n\r\n\r\r\r\n\n\n");
int length = data.remaining();
for(int i = 0; i<length-1; i++){
//partition 0 to i
ByteBuffer dataSeg = data.slice();
dataSeg.position(0);
dataSeg.limit(i);
assertThat("First "+i,parser.parse(dataSeg,false),is(false));
//partition i
dataSeg = data.slice();
dataSeg.position(i);
dataSeg.limit(i+1);
assertThat("Second "+i,parser.parse(dataSeg,false),is(false));
//partition i to length
dataSeg = data.slice();
dataSeg.position(i+1);
dataSeg.limit(length);
assertThat("Third "+i,parser.parse(dataSeg,true),is(true));
assertThat(handler.fields, Matchers.contains( "Content-Disposition: form-data; name=\"text\"","<<COMPLETE>>"
, "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""
, "Content-Type: text/plain","<<COMPLETE>>"
, "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\""
, "Content-Type: text/html","<<COMPLETE>>"
, "Field1: value1", "Field2: value2", "Field3: value3"
, "Field4: value4", "Field5: value5", "Field6: value6"
, "Field7: value7", "Field8: value8", "Field9: value 9", "<<COMPLETE>>"
, "Field1: value1","<<COMPLETE>>"));
assertThat(handler.contentString(), is(new String("text default"+"<<LAST>>"
+ "Content of a.txt.\n"+"<<LAST>>"
+ "<!DOCTYPE html><title>Content of a.html.</title>\n"+"<<LAST>>"
+ "<<LAST>>"
+ "But the amount of denudation which the strata have\n" +
"in many places suffered, independently of the rate\n" +
"of accumulation of the degraded matter, probably\n" +
"offers the best evidence of the lapse of time. I remember\n" +
"having been much struck with the evidence of\n" +
"denudation, when viewing volcanic islands, which\n" +
"have been worn by the waves and pared all round\n" +
"into perpendicular cliffs of one or two thousand feet\n" +
"in height; for the gentle slope of the lava-streams,\n" +
"due to their formerly liquid state, showed at a glance\n" +
"how far the hard, rocky beds had once extended into\n" +
"the open ocean.\n"+ "<<LAST>>")));
handler.clear();
parser.reset();
}
}
@Test
public void testGeneratedForm()
{
TestHandler handler = new TestHandler()
{
@Override
public boolean messageComplete()
{
return true;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
super.content(buffer,last);
return false;
}
@Override
public boolean headerComplete()
{
return false;
}
};
MultiPartParser parser = new MultiPartParser(handler,"WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW");
ByteBuffer data = BufferUtil.toBuffer(""
+ "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
"\r\n" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" +
"Content-Disposition: form-data; name=\"part1\"\r\n" +
"\n" +
"wNfミxVam﾿t\r\n" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" +
"Content-Disposition: form-data; name=\"part2\"\r\n" +
"\r\n" +
"&ᄈᄎ￙ᅱᅢO\r\n" +
"--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--");
parser.parse(data,true);
assertThat(parser.getState(), is(State.END));
assertThat(handler.fields.size(), is(2));
}
static class TestHandler implements MultiPartParser.Handler
{
List<String> fields = new ArrayList<>();
List<String> content = new ArrayList<>();
@Override
public void parsedField(String name, String value)
{
fields.add(name+": "+value);
}
public String contentString()
{
StringBuilder sb = new StringBuilder();
for(String s : content) sb.append(s);
return sb.toString();
}
@Override
public boolean headerComplete()
{
fields.add("<<COMPLETE>>");
return false;
}
@Override
public boolean content(ByteBuffer buffer, boolean last)
{
if (BufferUtil.hasContent(buffer))
content.add(BufferUtil.toString(buffer));
if (last)
content.add("<<LAST>>");
return last;
}
public void clear() {
fields.clear();
content.clear();
}
}
}

View File

@ -0,0 +1,297 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.jmh;
import java.io.File;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartFormInputStream;
import org.eclipse.jetty.http.MultiPartCaptureTest.MultipartExpectations;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.Rule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.profile.CompilerProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@State(Scope.Benchmark)
public class MultiPartBenchmark
{
public static final int MAX_FILE_SIZE = Integer.MAX_VALUE;
public static final int MAX_REQUEST_SIZE = Integer.MAX_VALUE;
public static final int FILE_SIZE_THRESHOLD = 50;
public int count = 0;
static String _contentType;
static File _file;
static int _numSections;
static int _numBytesPerSection;
public static List<String> data = new ArrayList<>();
static
{
// Capture of raw request body contents from various browsers
// simple form - 2 fields
data.add("browser-capture-form1-android-chrome");
data.add("browser-capture-form1-android-firefox");
data.add("browser-capture-form1-chrome");
data.add("browser-capture-form1-edge");
data.add("browser-capture-form1-firefox");
data.add("browser-capture-form1-ios-safari");
data.add("browser-capture-form1-msie");
data.add("browser-capture-form1-osx-safari");
// form submitted as shift-jis
data.add("browser-capture-sjis-form-edge");
data.add("browser-capture-sjis-form-msie");
// form submitted as shift-jis (with HTML5 specific hidden _charset_ field)
data.add("browser-capture-sjis-charset-form-edge");
data.add("browser-capture-sjis-charset-form-msie");
// form submitted with simple file upload
data.add("browser-capture-form-fileupload-android-chrome");
data.add("browser-capture-form-fileupload-android-firefox");
data.add("browser-capture-form-fileupload-chrome");
data.add("browser-capture-form-fileupload-edge");
data.add("browser-capture-form-fileupload-firefox");
data.add("browser-capture-form-fileupload-ios-safari");
data.add("browser-capture-form-fileupload-msie");
data.add("browser-capture-form-fileupload-safari");
// form submitted with 2 files (1 binary, 1 text) and 2 text fields
data.add("browser-capture-form-fileupload-alt-chrome");
data.add("browser-capture-form-fileupload-alt-edge");
data.add("browser-capture-form-fileupload-alt-firefox");
data.add("browser-capture-form-fileupload-alt-msie");
data.add("browser-capture-form-fileupload-alt-safari");
}
@Param({"UTIL","HTTP"})
public static String parserType;
@Setup(Level.Trial)
public static void setupTrial() throws Exception
{
_file = File.createTempFile("test01",null);
_file.deleteOnExit();
_numSections = 1;
_numBytesPerSection = 1024*1024*10;
_contentType = "multipart/form-data, boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW";
String initialBoundary = "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n";
String boundary = "\r\n--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n";
String closingBoundary = "\r\n--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--\r\n";
String headerStart = "Content-Disposition: form-data; name=\"";
for(int i=0; i<_numSections; i++) {
//boundary and headers
if(i==0)
Files.write(_file.toPath(), initialBoundary.getBytes(), StandardOpenOption.APPEND);
else
Files.write(_file.toPath(), boundary.getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), headerStart.getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), new String("part"+(i+1)).getBytes(), StandardOpenOption.APPEND);
Files.write(_file.toPath(), new String("\"\r\n\r\n").getBytes(), StandardOpenOption.APPEND);
//append random data
byte[] data = new byte[_numBytesPerSection];
new Random().nextBytes(data);
Files.write(_file.toPath(), data, StandardOpenOption.APPEND);
}
//closing boundary
Files.write(_file.toPath(), closingBoundary.getBytes(), StandardOpenOption.APPEND);
/*
// print out file to verify that it contains valid contents (just for testing)
InputStream in = Files.newInputStream(_file.toPath());
System.out.println();
while(in.available()>0) {
byte b[] = new byte[100];
int read = in.read(b,0,100);
for(int i=0; i<read; i++)
System.out.print((char)b[i]);
}
System.out.println();
//exit
throw new RuntimeException("Stop Here");
*/
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@SuppressWarnings("deprecation")
public long testLargeGenerated() throws Exception
{
Path multipartRawFile = _file.toPath();
Path outputDir = new File("/tmp").toPath();
MultipartConfigElement config = newMultipartConfigElement(outputDir);
try (InputStream in = Files.newInputStream(multipartRawFile))
{
switch(parserType)
{
case "HTTP":
{
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, _contentType, config, outputDir.toFile());
if(parser.getParts().size() != _numSections)
throw new IllegalStateException("Incorrect Parsing");
for(Part p : parser.getParts()) {
count += p.getSize();
}
}
break;
case "UTIL":
{
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in, _contentType,config,outputDir.toFile());
// TODO this is using the http version of part (which should be the same anyway)
if(parser.getParts().size() != _numSections)
throw new IllegalStateException("Incorrect Parsing");
for(Part p : parser.getParts()) {
count += p.getSize();
}
}
break;
default:
throw new IllegalStateException("Unknown parserType Parameter");
}
}
return count;
}
@TearDown(Level.Trial)
public static void stopTrial() throws Exception
{
_file = null;
}
private MultipartConfigElement newMultipartConfigElement(Path path)
{
return new MultipartConfigElement(path.toString(), MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD);
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@SuppressWarnings("deprecation")
public long testParser() throws Exception
{
for(String multiPart : data)
{
Path multipartRawFile = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".raw");
Path expectationPath = MavenTestingUtils.getTestResourcePathFile("multipart/" + multiPart + ".expected.txt");
Path outputDir = new File("/tmp").toPath();
MultipartExpectations multipartExpectations = new MultipartExpectations(expectationPath);
MultipartConfigElement config = newMultipartConfigElement(outputDir);
try (InputStream in = Files.newInputStream(multipartRawFile))
{
switch(parserType)
{
case "HTTP":
{
MultiPartFormInputStream parser = new MultiPartFormInputStream(in, multipartExpectations.contentType, config, outputDir.toFile());
for(Part p : parser.getParts()) {
count += p.getSize();
}
}
break;
case "UTIL":
{
org.eclipse.jetty.util.MultiPartInputStreamParser parser = new org.eclipse.jetty.util.MultiPartInputStreamParser(in,multipartExpectations.contentType,config,outputDir.toFile());
// TODO this is using the http version of part (which should be the same anyway)
for(Part p : parser.getParts()) {
count += p.getSize();
}
}
break;
default:
throw new IllegalStateException("Unknown parserType Parameter");
}
}
}
return count;
}
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(MultiPartBenchmark.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(10)
.forks(1)
.threads(1)
// .syncIterations(true) // Don't start all threads at same time
// .warmupTime(new TimeValue(10000,TimeUnit.MILLISECONDS))
// .measurementTime(new TimeValue(10000,TimeUnit.MILLISECONDS))
// .addProfiler(CompilerProfiler.class)
// .addProfiler(LinuxPerfProfiler.class)
// .addProfiler(LinuxPerfNormProfiler.class)
// .addProfiler(LinuxPerfAsmProfiler.class)
// .resultFormat(ResultFormatType.CSV)
.build();
new Runner(opt).run();
}
}

View File

@ -0,0 +1,9 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|248
Request-Header|Content-Type|multipart/form-data; boundary=DHbU6ChASebwm4iE8z9Lakv4ybMmkp
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|1
Part-ContainsContents|company|bob+%26+frank%27s+shoe+repair

View File

@ -0,0 +1,15 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22940
Request-Header|Content-Type|multipart/form-data; boundary=owr6UQGvVNunA_sx2AsizBtyq_uK-OjsQXrF
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|6
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510
Part-ContainsContents|company|bob & frank's shoe repair
Part-ContainsContents|power|о𝗋𝖾
Part-ContainsContents|japanese|オープンソース
Part-ContainsContents|hello|日食桟橋
Part-Filename|upload_file|filename
Part-Sha1sum|upload_file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,15 @@
Request-Header|Accept-Encoding|gzip
Request-Header|Connection|close
Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1275gffetpxz8o0q
Request-Header|Host|localhost:9090
Request-Header|Transfer-Encoding|chunked
Request-Header|User-Agent|Jetty/9.4.9.v20180320
Request-Header|X-BrowserId|jetty-client
Parts-Count|6
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510
Part-ContainsContents|company|bob & frank's shoe repair
Part-ContainsContents|power|о𝗋𝖾
Part-ContainsContents|japanese|オープンソース
Part-ContainsContents|hello|日食桟橋
Part-Filename|upload_file|filename
Part-Sha1sum|upload_file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,8 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|1815
Request-Header|Content-Type|multipart/form-data; boundary=QW3F8Fg64P2J2dpfEKGKlX0Q9QF2a8SK_7YH
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|10

View File

@ -0,0 +1,8 @@
Request-Header|Accept-Encoding|gzip
Request-Header|Connection|close
Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary14beb4to333d91v8
Request-Header|Host|localhost:9090
Request-Header|Transfer-Encoding|chunked
Request-Header|User-Agent|Jetty/9.4.9.v20180320
Request-Header|X-BrowserId|jetty-client
Parts-Count|10

View File

@ -0,0 +1,11 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|31148
Request-Header|Content-Type|multipart/form-data; boundary=qqr2YBBR31U4xVib4vaVuIsrwNY1iw
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|169
Part-ContainsContents|count|168
Part-ContainsContents|persian-UTF-8|برج بابل
Part-ContainsContents|persian-CESU-8|برج بابل

View File

@ -0,0 +1,11 @@
Request-Header|Accept-Encoding|gzip
Request-Header|Connection|close
Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1jcfdl0zps9nf362
Request-Header|Host|localhost:9090
Request-Header|Transfer-Encoding|chunked
Request-Header|User-Agent|Jetty/9.4.9.v20180320
Request-Header|X-BrowserId|jetty-client
Parts-Count|169
Part-ContainsContents|count|168
Part-ContainsContents|persian-UTF-8|برج بابل
Part-ContainsContents|persian-CESU-8|برج بابل

View File

@ -0,0 +1,21 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate, br
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22759
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryafpkbdzB5Ciqre2z
Request-Header|Cookie|visited=yes
Request-Header|DNT|1
Request-Header|Host|localhost:9090
Request-Header|Origin|http://localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36
Parts-Count|4
Part-ContainsContents|description|the larger icon
Part-ContainsContents|alternate|text.raw
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8
Part-Filename|file-alt|text.raw
Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22824
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e21c038151054
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299
Parts-Count|4
Part-ContainsContents|description|the larger icon
Part-ContainsContents|alternate|text.raw
Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8
Part-Filename|file-alt|C:\Users\joakim\Pictures\text.raw
Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22774
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------23281168279961
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0
Parts-Count|4
Part-ContainsContents|description|the larger icon
Part-ContainsContents|alternate|text.raw
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8
Part-Filename|file-alt|text.raw
Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22814
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e226692109c
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload-multi.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Parts-Count|4
Part-ContainsContents|description|the larger icon
Part-ContainsContents|alternate|text.raw
Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8
Part-Filename|file-alt|C:\Users\joakim\Pictures\text.raw
Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b

View File

@ -0,0 +1,18 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22774
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryEQhxWUv9r38x3LyB
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form-fileupload-multi.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Parts-Count|4
Part-ContainsContents|description|the larger icon
Part-ContainsContents|alternate|text.raw
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8
Part-Filename|file-alt|text.raw
Part-Sha1sum|file-alt|5fb031816a27d80cc88c390819addab0ec3c189b

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22054
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary2oBNepLIldUG8YwL
Request-Header|DNT|1
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22105
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------2117751712556306154183865432
Request-Header|Host|192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,18 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate, br
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22054
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundarylxcKjAyTlRs3jNP2
Request-Header|Cookie|visited=yes
Request-Header|DNT|1
Request-Header|Host|localhost:9090
Request-Header|Origin|http://localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22085
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e225f6151054
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22063
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------24464570528145
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,15 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22074
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary5trdx3OwYr8uMtbA
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|66A4F66B-9B37-4F69-86A7-456547EBF079.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22082
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e223ef2109c
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form-fileupload.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|C:\Users\joakim\Pictures\jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,15 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|22054
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryWl9yEX5Fas0SI2xc
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form-fileupload.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Parts-Count|2
Part-ContainsContents|description|the larger icon
Part-Filename|file|jetty-avatar-256.png
Part-Sha1sum|file|e75b73644afe9b234d70da9ff225229de68cdff8

View File

@ -0,0 +1,16 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|245
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryD4GyXQgjBRmK3aBz
Request-Header|DNT|1
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36
Parts-Count|2
Part-ContainsContents|user|Androiduser
Part-ContainsContents|comment|Dyac!

View File

@ -0,0 +1,13 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|306
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------6390283156237600831344307695
Request-Header|Host|192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0
Parts-Count|2
Part-ContainsContents|user|androidfireuser
Part-ContainsContents|comment|More to say

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate, br
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|256
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary46EP6zTN86hbbaJC
Request-Header|Cookie|visited=yes
Request-Header|DNT|1
Request-Header|Host|localhost:9090
Request-Header|Origin|http://localhost:9090
Request-Header|Referer|http://localhost:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36
Parts-Count|2
Part-ContainsContents|user|joe
Part-ContainsContents|comment|this is a simple comment

View File

@ -0,0 +1,13 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|267
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e25e1e151054
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299
Parts-Count|2
Part-ContainsContents|user|anotheruser
Part-ContainsContents|comment|with something to say

View File

@ -0,0 +1,13 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|258
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------41184676334
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0
Parts-Count|2
Part-ContainsContents|user|fireuser
Part-ContainsContents|comment|with detailed message

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|268
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundary56m5uMm4gNcn4rL1
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1
Parts-Count|2
Part-ContainsContents|user|UseriPad
Part-ContainsContents|comment|This form isnt pretty

View File

@ -0,0 +1,13 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|285
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e21b6f2109c
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/form.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Parts-Count|2
Part-ContainsContents|user|msieuser
Part-ContainsContents|comment|with information that they think is important

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|284
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryjwqONTsAFgubfMZc
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Parts-Count|2
Part-ContainsContents|user|safariuser
Part-ContainsContents|comment|with rambling thoughts about bellybutton lint

View File

@ -0,0 +1,12 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|1203
Request-Header|Content-Type|multipart/form-data; boundary=Cku4UvJrPFCXkXjge2a2Y2sgq1bbOa
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|4
Part-ContainsContents|reporter|<user@company.com>
Part-ContainsContents|timestamp|2018-03-21T18:52:18+00:00
Part-ContainsContents|comments|this couldn't be parsed
Part-ContainsContents|attachment|banana

View File

@ -0,0 +1,12 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|1577
Request-Header|Content-Type|multipart/form-data; boundary=xDeLGHDDsXrlJSXfqDmg5IRop7auqTTBXuI
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|4
Part-ContainsContents|reporter|<user@company.com>
Part-ContainsContents|timestamp|2018-03-21T19:00:18+00:00
Part-ContainsContents|comments|this also couldn't be parsed
Part-ContainsContents|attachment|cherry

View File

@ -0,0 +1,12 @@
Request-Header|Accept-Encoding|gzip
Request-Header|Connection|close
Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1uz60vid2bq7x1t9
Request-Header|Host|localhost:9090
Request-Header|Transfer-Encoding|chunked
Request-Header|User-Agent|Jetty/9.4.9.v20180320
Request-Header|X-BrowserId|jetty-client
Parts-Count|4
Part-ContainsContents|reporter|<user@company.com>
Part-ContainsContents|timestamp|2018-03-21T18:52:18+00:00
Part-ContainsContents|comments|this couldn't be parsed
Part-ContainsContents|attachment|banana

View File

@ -0,0 +1,9 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|173
Request-Header|Content-Type|multipart/form-data; boundary=xE8WoYDcbqAfj08bxPk669iK22hMMlZL
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|1
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510

View File

@ -0,0 +1,12 @@
Request-Header|Accept-Encoding|gzip
Request-Header|Connection|close
Request-Header|Content-Type|multipart/form-data; boundary=JettyHttpClientBoundary1shlqpw2yahae6jf
Request-Header|Host|localhost:9090
Request-Header|Transfer-Encoding|chunked
Request-Header|User-Agent|Jetty/9.4.9.v20180320
Request-Header|X-BrowserId|jetty-client
Parts-Count|1
# Start of sequence
Part-ContainsContents|pi|3.14159 26535 89793 23846 26433 83279 50288
# End of sequence
Part-ContainsContents|pi|81592 05600 10165 52563 7567

View File

@ -0,0 +1,9 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|240
Request-Header|Content-Type|multipart/form-data; boundary=L8vdau8TpP0o-AYJDjCuYFQYnjB5gcHIFyap
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|1
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510

View File

@ -0,0 +1,10 @@
Request-Header|Accept-Encoding|gzip,deflate
Request-Header|Connection|keep-alive
Request-Header|Content-Length|406
Request-Header|Content-Type|multipart/form-data; boundary=u7tfLQaHJEHHUJjnVDbFdc_Oqz4jmkA25mgWd
Request-Header|Host|localhost:9090
Request-Header|User-Agent|Apache-HttpClient/4.5.5 (Java/1.8.0_162)
Request-Header|X-BrowserId|apache-httpcomp
Parts-Count|2
Part-ContainsContents|japanese|オープンソース
Part-ContainsContents|hello|日食桟橋

View File

@ -0,0 +1,17 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|354
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryN7pYBoDaXhEcUl13
Request-Header|DNT|1
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|430
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------117031256520586657911714164254
Request-Header|Host|192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,18 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate, br
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|354
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryDHtjXxgNUcgLjcKs
Request-Header|Cookie|visited=yes
Request-Header|DNT|1
Request-Header|Host|localhost:9090
Request-Header|Origin|http://localhost:9090
Request-Header|Referer|http://localhost:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|362
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e227e17151054
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/sjis-form-charset.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299
Parts-Count|3
Part-ContainsContents|_charset_|utf-8
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ戆タ

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|370
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------114782935826962
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,15 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|354
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryvshQXGBfIsRjfMBN
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (iPad; CPU OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0 Mobile/15D100 Safari/604.1
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,14 @@
Request-Header|Accept|text/html, application/xhtml+xml, image/jxr, */*
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US
Request-Header|Cache-Control|no-cache
Request-Header|Connection|keep-alive
Request-Header|Content-Length|358
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------7e226e1b2109c
Request-Header|Host|localhost:9090
Request-Header|Referer|http://localhost:9090/sjis-form-charset.html
Request-Header|User-Agent|Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Parts-Count|3
Part-ContainsContents|_charset_|utf-8
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ戆タ

View File

@ -0,0 +1,15 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-us
Request-Header|Connection|keep-alive
Request-Header|Content-Length|354
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryHFCTTESrC7sXQ2Gf
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form-charset.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Parts-Count|3
Part-ContainsContents|_charset_|Shift_JIS
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ&#25094;タ

View File

@ -0,0 +1,16 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.9
Request-Header|Cache-Control|max-age=0
Request-Header|Connection|keep-alive
Request-Header|Content-Length|249
Request-Header|Content-Type|multipart/form-data; boundary=----WebKitFormBoundaryjJR29nbr1TDUu2yh
Request-Header|DNT|1
Request-Header|Host|192.168.0.119:9090
Request-Header|Origin|http://192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36
Parts-Count|2
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ戆タ

View File

@ -0,0 +1,13 @@
Request-Header|Accept|text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Request-Header|Accept-Encoding|gzip, deflate
Request-Header|Accept-Language|en-US,en;q=0.5
Request-Header|Connection|keep-alive
Request-Header|Content-Length|303
Request-Header|Content-Type|multipart/form-data; boundary=---------------------------18591390852002031541755421242
Request-Header|Host|192.168.0.119:9090
Request-Header|Referer|http://192.168.0.119:9090/sjis-form.html
Request-Header|Upgrade-Insecure-Requests|1
Request-Header|User-Agent|Mozilla/5.0 (Android 8.1.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0
Parts-Count|2
Part-ContainsContents|japanese|健治
Part-ContainsContents|hello|ャユ戆タ

Some files were not shown because too many files have changed in this diff Show More