Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x

This commit is contained in:
Joakim Erdfelt 2018-04-20 11:32:46 -05:00
commit 8763c6065d
290 changed files with 8299 additions and 1620 deletions

1
.gitattributes vendored
View File

@ -6,3 +6,4 @@
*.xml eol=lf
Jenkinsfile eol=lf
*.js eol=lf
*.raw binary

27
Jenkinsfile vendored
View File

@ -1,6 +1,6 @@
#!groovy
def jdks = ["jdk8", "jdk9"]
def jdks = ["jdk8","jdk9","jdk10","jdk11"]
def oss = ["linux"] //windows? ,"linux-docker"
def builds = [:]
for (def os in oss) {
@ -15,8 +15,10 @@ def getFullBuild(jdk, os) {
return {
node(os) {
// System Dependent Locations
def mvntool = tool name: 'maven3', type: 'hudson.tasks.Maven$MavenInstallation'
def mvntool = tool name: 'maven3.5', type: 'hudson.tasks.Maven$MavenInstallation'
def jdktool = tool name: "$jdk", type: 'hudson.model.JDK'
def mvnName = 'maven3.5'
def localRepo = ".repository" // "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}"
// Environment
List mvnEnv = ["PATH+MVN=${mvntool}/bin", "PATH+JDK=${jdktool}/bin", "JAVA_HOME=${jdktool}/", "MAVEN_HOME=${mvntool}"]
@ -38,11 +40,11 @@ def getFullBuild(jdk, os) {
withEnv(mvnEnv) {
timeout(time: 15, unit: 'MINUTES') {
withMaven(
maven: 'maven3',
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
globalMavenSettingsConfig: 'oss-settings.xml',
mavenLocalRepo: "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}") {
mavenLocalRepo: localRepo) {
sh "mvn -V -B clean install -DskipTests -T6"
}
@ -60,11 +62,11 @@ def getFullBuild(jdk, os) {
withEnv(mvnEnv) {
timeout(time: 20, unit: 'MINUTES') {
withMaven(
maven: 'maven3',
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
globalMavenSettingsConfig: 'oss-settings.xml',
mavenLocalRepo: "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}") {
mavenLocalRepo: localRepo) {
sh "mvn -V -B javadoc:javadoc -T5"
}
}
@ -82,12 +84,12 @@ def getFullBuild(jdk, os) {
timeout(time: 90, unit: 'MINUTES') {
// Run test phase / ignore test failures
withMaven(
maven: 'maven3',
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
//options: [invokerPublisher(disabled: false)],
globalMavenSettingsConfig: 'oss-settings.xml',
mavenLocalRepo: "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}") {
mavenLocalRepo: localRepo) {
//
sh "mvn -V -B install -Dmaven.test.failure.ignore=true -Prun-its -T3 -e -Dmaven.repo.local=${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER} -Pmongodb"
}
@ -134,17 +136,14 @@ def getFullBuild(jdk, os) {
try
{
stage ("Compact3 - ${jdk}") {
dir("aggregates/jetty-all-compact3") {
withEnv(mvnEnv) {
withMaven(
maven: 'maven3',
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
globalMavenSettingsConfig: 'oss-settings.xml',
mavenLocalRepo: "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}") {
sh "mvn -V -B -Pcompact3 clean install -T5"
}
mavenLocalRepo: localRepo) {
sh "mvn -f aggregates/jetty-all-compact3 -V -B -Pcompact3 clean install -T5"
}
}
}

View File

@ -24,7 +24,7 @@
<Export-Package>org.eclipse.jetty.apache.jsp.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}",
org.eclipse.jetty.jsp.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"
</Export-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader;osgi.serviceloader=javax.servlet.ServletContainerInitializer,osgi.serviceloader;osgi.serviceloader=org.apache.juli.logging.Log</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -24,7 +24,7 @@
<configuration>
<instructions>
<Import-Package>org.eclipse.jetty.alpn;resolution:=optional,*</Import-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Client)";cardinality:=multiple</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional, osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Client)";resolution:=optional;cardinality:=multiple</Require-Capability>
</instructions>
</configuration>
</execution>

View File

@ -46,7 +46,7 @@
<Bundle-Description>Conscrypt Client ALPN</Bundle-Description>
<Import-Package>org.conscrypt;version="${conscrypt.version}",*</Import-Package>
<Export-Package>*</Export-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Client</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -49,7 +49,7 @@
<instructions>
<Bundle-Description>Conscrypt ALPN</Bundle-Description>
<Import-Package>org.conscrypt;version="${conscrypt.version}",*</Import-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader;osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -35,7 +35,7 @@
<instructions>
<Bundle-Description>JDK9 Client ALPN</Bundle-Description>
<Export-Package>*</Export-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Client</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -33,7 +33,7 @@
<configuration>
<instructions>
<Bundle-Description>JDK9 Server ALPN</Bundle-Description>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader;osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -62,7 +62,7 @@
<Bundle-Description>OpenJDK8 Client ALPN</Bundle-Description>
<Import-Package>org.eclipse.jetty.alpn;version="${alpn.majorVersion}.${alpn.minorVersion}.${alpn.incrementalVersion}",*</Import-Package>
<Export-Package>*</Export-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Client</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -67,7 +67,7 @@
<Bundle-Description>OpenJDK8 Server ALPN</Bundle-Description>
<Export-Package>*</Export-Package>
<Import-Package>org.eclipse.jetty.alpn;version="${alpn.majorVersion}.${alpn.minorVersion}.${alpn.incrementalVersion}",*</Import-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

View File

@ -49,7 +49,7 @@
<Bundle-SymbolicName>${bundle-symbolic-name};singleton:=true</Bundle-SymbolicName>
<Export-Package>org.eclipse.jetty.alpn.server,*</Export-Package>
<Import-Package>org.eclipse.jetty.alpn;version="${alpn.majorVersion}.${alpn.minorVersion}.${alpn.incrementalVersion}",*</Import-Package>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional, osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple</Require-Capability>
</instructions>
</configuration>
</plugin>

View File

@ -0,0 +1,7 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.12.v20180117/alpn-boot-8.1.12.v20180117.jar|lib/alpn/alpn-boot-8.1.12.v20180117.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.12.v20180117.jar

View File

@ -0,0 +1,7 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.12.v20180117/alpn-boot-8.1.12.v20180117.jar|lib/alpn/alpn-boot-8.1.12.v20180117.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.12.v20180117.jar

View File

@ -21,7 +21,7 @@
<configuration>
<instructions>
<Import-Package>org.objectweb.asm;version="[5.0,7)",*</Import-Package>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional</Require-Capability>
</instructions>
</configuration>
</plugin>

View File

@ -85,14 +85,14 @@ public class AnnotationParser
public static int asmVersion ()
{
int asmVersion = ASM_OPCODE_VERSION;
Package asm = Package.getPackage("org.objectweb.asm");
Package asm = Opcodes.class.getPackage();
if (asm == null)
LOG.warn("Unknown asm runtime version, assuming version {}", asmVersion);
else
{
String s = asm.getImplementationVersion();
if (s==null)
LOG.warn("Unknown asm runtime version, assuming version {}", asmVersion);
LOG.warn("Unknown asm implementation version, assuming version {}", asmVersion);
else
{
int dot = s.indexOf('.');

View File

@ -29,7 +29,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
@ -48,6 +47,7 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.Assert;
import org.junit.Rule;
@ -221,6 +221,17 @@ public class TestAnnotationParser
// Should throw no exceptions, and skip the META-INF/versions/9/* files
}
@Test
public void testJep238MultiReleaseInJar_JDK10() throws Exception
{
File jdk10Jar = MavenTestingUtils.getTestResourceFile("jdk10/multirelease-10.jar");
AnnotationParser parser = new AnnotationParser();
DuplicateClassScanHandler handler = new DuplicateClassScanHandler();
Set<Handler> handlers = Collections.singleton(handler);
parser.parse(handlers, new PathResource(jdk10Jar));
// Should throw no exceptions
}
@Test
public void testBasedirExclusion() throws Exception
{

View File

@ -0,0 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.annotations.LEVEL=DEBUG

View File

@ -47,7 +47,7 @@ public class JettyRunTask extends Task
private File tempDirectory;
/** List of web applications to be deployed. */
private List<AntWebAppContext> webapps = new ArrayList<AntWebAppContext>();
private List<AntWebAppContext> webapps = new ArrayList<>();
/** Location of jetty.xml file. */
private File jettyXml;
@ -147,20 +147,17 @@ public class JettyRunTask extends Task
{
try
{
this.requestLog = (RequestLog) Class.forName(className).newInstance();
}
catch (InstantiationException e)
{
throw new BuildException("Request logger instantiation exception: " + e);
}
catch (IllegalAccessException e)
{
throw new BuildException("Request logger instantiation exception: " + e);
this.requestLog = (RequestLog) Class.forName(className).getDeclaredConstructor().newInstance();
}
catch (ClassNotFoundException e)
{
throw new BuildException("Unknown request logger class: " + className);
}
catch (Exception e)
{
throw new BuildException("Request logger instantiation exception: " + e);
}
}
public String getRequestLog()

View File

@ -42,7 +42,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]*)\"(.*)", Pattern.CASE_INSENSITIVE);
private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+(.*,\\s*)?realm=\"([^\"]*)\"\\s*,?\\s*(.*)", Pattern.CASE_INSENSITIVE);
private final HttpClient client;
private final int maxContentLength;
@ -241,8 +241,16 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
if (matcher.matches())
{
String type = matcher.group(1);
String realm = matcher.group(2);
String params = matcher.group(3);
String realm = matcher.group(3);
String beforeRealm = matcher.group(2);
String afterRealm = matcher.group(4);
String params;
if (beforeRealm != null)
params = beforeRealm + afterRealm;
else
params = afterRealm;
Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
result.add(headerInfo);
}

View File

@ -344,6 +344,8 @@ The ALPN implementation, relying on modifications of OpenJDK classes, updates ev
|1.8.0u152 |8.1.11.v20170118
|1.8.0u161 |8.1.12.v20180117
|1.8.0u162 |8.1.12.v20180117
|1.8.0u171 |8.1.12.v20180117
|1.8.0u172 |8.1.12.v20180117
|=============================
[[alpn-build]]

View File

@ -100,7 +100,7 @@ To test a Jetty release, complete the following steps for each release you want
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty-version>7.6.2.v20120308</jetty-version>
<jetty-plugin-version>${jetty-version}</jetty-plugin-version>
<slf4j-version>1.6.4</slf4j-version>
<slf4j.version>1.6.4</slf4j.version>
<spring-version>3.1.0.RELEASE</spring-version>
</properties>
<repositories>

View File

@ -303,6 +303,19 @@
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-servlet-src-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>javax.servlet</includeGroupIds>
<includeTypes>jar</includeTypes>
<classifier>sources</classifier>
<outputDirectory>${source-assembly-directory}/lib/servlet-api</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-annotations-deps</id>
<phase>generate-resources</phase>

View File

@ -52,7 +52,7 @@
<configuration>
<instructions>
<Bundle-Description>Jetty Http SPI</Bundle-Description>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=com.sun.net.httpserver.spi.HttpServerProvider</Provide-Capability>
<_nouses>true</_nouses>
</instructions>

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>
@ -38,9 +55,6 @@
<configuration>
<instructions>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
<!--
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
-->
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder</Provide-Capability>
</instructions>
</configuration>
@ -64,7 +78,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

@ -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

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