Issue #1027 - Adding jetty-http version of newer MultiPartParsingTest
Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
c0dcf9a0a2
commit
2004f0eb78
|
@ -0,0 +1,284 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.DigestOutputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
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.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class MultiPartParsingTest
|
||||||
|
{
|
||||||
|
|
||||||
|
public static final int MAX_FILE_SIZE = 60 * 1024;
|
||||||
|
public static final int MAX_REQUEST_SIZE = 1024 * 1024;
|
||||||
|
public static final int FILE_SIZE_THRESHOLD = 50;
|
||||||
|
|
||||||
|
@Parameterized.Parameters(name = "{0}")
|
||||||
|
public static List<Object[]> data()
|
||||||
|
{
|
||||||
|
List<Object[]> ret = new ArrayList<>();
|
||||||
|
|
||||||
|
ret.add(new String[]{"multipart-text-files"});
|
||||||
|
ret.add(new String[]{"multipart-base64"});
|
||||||
|
ret.add(new String[]{"multipart-base64-long"});
|
||||||
|
ret.add(new String[]{"multipart-complex"});
|
||||||
|
ret.add(new String[]{"multipart-duplicate-names-1"});
|
||||||
|
ret.add(new String[]{"multipart-encoding-mess"});
|
||||||
|
ret.add(new String[]{"multipart-inside-itself"});
|
||||||
|
ret.add(new String[]{"multipart-inside-itself-binary"});
|
||||||
|
ret.add(new String[]{"multipart-number-browser"});
|
||||||
|
ret.add(new String[]{"multipart-number-strict"});
|
||||||
|
ret.add(new String[]{"multipart-sjis"});
|
||||||
|
ret.add(new String[]{"multipart-strange-quoting"});
|
||||||
|
ret.add(new String[]{"multipart-unicode-names"});
|
||||||
|
ret.add(new String[]{"multipart-uppercase"});
|
||||||
|
ret.add(new String[]{"multipart-x-www-form-urlencoded"});
|
||||||
|
ret.add(new String[]{"multipart-zencoding"});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TestingDir testingDir = new TestingDir();
|
||||||
|
|
||||||
|
private final Path multipartRawFile;
|
||||||
|
private final MultipartExpectations multipartExpectations;
|
||||||
|
|
||||||
|
public MultiPartParsingTest(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 testParse() throws IOException, NoSuchAlgorithmException
|
||||||
|
{
|
||||||
|
Path outputDir = testingDir.getEmptyPathDir();
|
||||||
|
MultipartConfigElement config = newMultipartConfigElement(outputDir);
|
||||||
|
try (InputStream in = Files.newInputStream(multipartRawFile))
|
||||||
|
{
|
||||||
|
MultiPartInputStreamParser parser = new MultiPartInputStreamParser(in, multipartExpectations.contentType, config, outputDir.toFile());
|
||||||
|
parser.parse();
|
||||||
|
|
||||||
|
// Evaluate Count
|
||||||
|
if (multipartExpectations.partCount >= 0)
|
||||||
|
{
|
||||||
|
assertThat("Mulitpart.parts.size", parser.getParts().size(), is(multipartExpectations.partCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate expected Contents
|
||||||
|
for (NameValue expected : multipartExpectations.partContainsContents)
|
||||||
|
{
|
||||||
|
Part part = parser.getPart(expected.name);
|
||||||
|
assertThat("Part[" + expected.name + "]", part, is(notNullValue()));
|
||||||
|
try (InputStream partInputStream = part.getInputStream())
|
||||||
|
{
|
||||||
|
String charset = getCharsetFromContentType(part.getContentType(), UTF_8);
|
||||||
|
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 = parser.getPart(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 = parser.getPart(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());
|
||||||
|
assertThat("Part[" + expected.name + "].sha1sum", actualSha1sum, containsString(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, Charset defaultCharset)
|
||||||
|
{
|
||||||
|
if(StringUtil.isBlank(contentType))
|
||||||
|
{
|
||||||
|
return defaultCharset.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="JuH4rALGPJfmAquncS_U1du8s59GjKKiG9a8"
|
||||||
|
Parts-Count|1
|
||||||
|
Part-Filename|png|jetty-avatar-256.png
|
||||||
|
Part-Sha1sum|png|e75b73644afe9b234d70da9ff225229de68cdff8
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp"
|
||||||
|
Parts-Count|1
|
||||||
|
Part-Filename|png|jetty-avatar-256.png
|
||||||
|
Part-Sha1sum|png|e75b73644afe9b234d70da9ff225229de68cdff8
|
Binary file not shown.
|
@ -0,0 +1,9 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="PMyKOsh8JrSZm-rUF8EJej42yqbh-UWw9FG-"
|
||||||
|
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
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="mToTaXZk2RQZb6TrQRLOgqW-44MPpVbs5wyJTlyl"
|
||||||
|
Parts-Count|10
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="CVnTR46ddexmZjsh1ORHg9QbCOiLhUYl"
|
||||||
|
Parts-Count|168
|
||||||
|
Part-ContainsContents|persian-UTF-8|برج بابل
|
||||||
|
Part-ContainsContents|persian-CESU-8|برج بابل
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="94GJ2MW4vpjh92qj-CHjUUIwb9X8Y2LqkU2Yxn0vTU2oHy5jk6_Kpxn2aE9EokEiRGox4eWfAYP8-"
|
||||||
|
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
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="kCFSwstIsIZkZtQ5JM7193kJYcK5WkyvoxsGjx5eCuVFbaeUZ9oK57JCp_p5wP86S5nY4re5x"
|
||||||
|
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
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="RjYiyHVV9Phv7tYylzT1f94fvTC-s7oNKwB9_Y"
|
||||||
|
Parts-Count|1
|
||||||
|
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="P6Uyk-Vikfbk_NqTfVF4DwNXrIxpN451pD1"
|
||||||
|
Parts-Count|1
|
||||||
|
Part-ContainsContents|pi|3.14159265358979323846264338327950288419716939937510
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="VA2isIGmYNkgC3qrrGXnlQcWO17WB3a4npVQey"
|
||||||
|
Parts-Count|2
|
||||||
|
Part-ContainsContents|japanese|健治
|
||||||
|
Part-ContainsContents|hello|ャユ戆タ
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="tR61vgdxSzzv2FDycET2lt-OUZ1IW1GqA"
|
||||||
|
Parts-Count|4
|
||||||
|
Part-ContainsContents|and "I" quote|Value 1
|
||||||
|
Part-ContainsContents|and+%22I%22+quote|Value 2
|
||||||
|
Part-ContainsContents|value"; what="whoa"|Value 3
|
Binary file not shown.
|
@ -0,0 +1,9 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="ny9C5eIZL7fsfPY9ONPy8Lxb6tkgEv1"
|
||||||
|
Parts-Count|3
|
||||||
|
Part-ContainsContents|text|text default
|
||||||
|
Part-ContainsContents|file1|Content of a.txt
|
||||||
|
Part-ContainsContents|file2|<!DOCTYPE html><title>Content of a.html
|
||||||
|
Part-Filename|file1|a.txt
|
||||||
|
Part-Filename|file2|a.html
|
||||||
|
Part-Sha1sum|file1|588A0F273CB5AFE9C8D76DD081812E672F2061E2
|
||||||
|
Part-Sha1sum|file2|9A9005159AB90A6D2D9BACB5414EFE932F0CED85
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="1R40qTSaEjDJPcArQiccT7vdpp0l02248R"
|
||||||
|
Parts-Count|2
|
||||||
|
Part-ContainsContents|こんにちは世界|Greetings 1
|
||||||
|
Part-ContainsContents|%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E4%B8%96%E7%95%8C|Greetings 2
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="8Q4MHJ3LWIQEQQ_OXYU5U9ZLYEH60_CFZQYANCZ"
|
||||||
|
Parts-Count|2
|
||||||
|
Part-ContainsContents|STATE|TEXAS
|
||||||
|
Part-ContainsContents|CITY|AUSTIN
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="qjIkwQOhaYfC2VEcL5j-9sjEK1FIsYMd5"
|
||||||
|
Parts-Count|1
|
||||||
|
Part-ContainsContents|company|bob & frank's shoe repair
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,8 @@
|
||||||
|
Content-Type|multipart/form-data; boundary="UuAU1liVuDVE7wfJUYE72PUF9DZafZ"
|
||||||
|
Parts-Count|4
|
||||||
|
Part-ContainsContents|zalgo-8|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞
|
||||||
|
Part-ContainsContents|zalgo-16|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞
|
||||||
|
Part-ContainsContents|zalgo-16-be|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞
|
||||||
|
Part-ContainsContents|zalgo-16-le|y͔͕͍o̪̞͎̥͇̤̕u'̛̰̫̳̰v̧̘̪̠̟̟e̥͈̱ ̥̠͇͎͕̜s̤e̺e̙ͅņ̜ ̲̟͝za̴͖̱̲͈̘l͖̖͓̙̮͔g͕̞͖͘o͕̤͈̗ ̯̲̹̲͓b͙͟e̞͎̜̗͈͉̭͞f̸or̰̩e̡̝̺,̸͕̙̥̼͇̜ ̪͇̹r̘̪ͅị͔̥͈ͅg̠̟̯͖̦͇ht͖̪͍͚̖͡?͙̝͖̞
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue