Merge pull request #4680 from eclipse/jetty-9.4.x-4673-MultiPart

Issue #4673 - fix MultiPart parsing for content split in boundary
This commit is contained in:
Lachlan 2020-03-19 08:49:07 +11:00 committed by GitHub
commit 6dc5792301
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 158 additions and 50 deletions

View File

@ -23,6 +23,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
@ -33,6 +35,8 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.Test;
@ -885,6 +889,97 @@ public class MultiPartFormInputStreamTest
assertEquals("the end", baos.toString("US-ASCII"));
}
@Test
public void testFragmentation() throws IOException
{
String contentType = "multipart/form-data, boundary=----WebKitFormBoundaryhXfFAMfUnUKhmqT8";
String payload1 =
"------WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" +
"Content-Disposition: form-data; name=\"field1\"\r\n\r\n" +
"value1" +
"\r\n--";
String payload2 = "----WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" +
"Content-Disposition: form-data; name=\"field2\"\r\n\r\n" +
"value2" +
"\r\n------WebKitFormBoundaryhXfFAMfUnUKhmqT8--\r\n";
// Split the content into separate reads, with the content broken up on the boundary string.
AppendableInputStream stream = new AppendableInputStream();
stream.append(payload1);
stream.append("");
stream.append(payload2);
stream.endOfContent();
MultipartConfigElement config = new MultipartConfigElement(_dirname);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(stream, contentType, config, _tmpDir);
mpis.setDeleteOnExit(true);
// Check size.
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
// Check part content.
assertThat(IO.toString(mpis.getPart("field1").getInputStream()), is("value1"));
assertThat(IO.toString(mpis.getPart("field2").getInputStream()), is("value2"));
}
static class AppendableInputStream extends InputStream
{
private static final ByteBuffer EOF = ByteBuffer.allocate(0);
private final BlockingArrayQueue<ByteBuffer> buffers = new BlockingArrayQueue<>();
private ByteBuffer current;
public void append(String data)
{
append(data.getBytes(StandardCharsets.US_ASCII));
}
public void append(byte[] data)
{
buffers.add(BufferUtil.toBuffer(data));
}
public void endOfContent()
{
buffers.add(EOF);
}
@Override
public int read() throws IOException
{
byte[] buf = new byte[1];
while (true)
{
int len = read(buf, 0, 1);
if (len < 0)
return -1;
if (len > 0)
return buf[0];
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
if (current == null)
current = buffers.poll();
if (current == EOF)
return -1;
if (BufferUtil.isEmpty(current))
{
current = null;
return 0;
}
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
buffer.flip();
int read = BufferUtil.append(buffer, current);
if (BufferUtil.isEmpty(current))
current = buffers.poll();
return read;
}
}
@Test
public void testQuotedPrintableEncoding() throws Exception
{

View File

@ -36,9 +36,9 @@ import java.util.Arrays;
*/
public class SearchPattern
{
static final int alphabetSize = 256;
private int[] table;
private byte[] pattern;
private static final int ALPHABET_SIZE = 256;
private final int[] table = new int[ALPHABET_SIZE];
private final byte[] pattern;
/**
* Produces a SearchPattern instance which can be used
@ -70,16 +70,11 @@ public class SearchPattern
private SearchPattern(byte[] pattern)
{
this.pattern = pattern;
if (pattern.length == 0)
throw new IllegalArgumentException("Empty Pattern");
//Build up the pre-processed table for this pattern.
table = new int[alphabetSize];
for (int i = 0; i < table.length; ++i)
{
table[i] = pattern.length;
}
Arrays.fill(table, pattern.length);
for (int i = 0; i < pattern.length - 1; ++i)
{
table[0xff & pattern[i]] = pattern.length - 1 - i;
@ -97,7 +92,7 @@ public class SearchPattern
*/
public int match(byte[] data, int offset, int length)
{
validate(data, offset, length);
validateArgs(data, offset, length);
int skip = offset;
while (skip <= offset + length - pattern.length)
@ -125,7 +120,7 @@ public class SearchPattern
*/
public int endsWith(byte[] data, int offset, int length)
{
validate(data, offset, length);
validateArgs(data, offset, length);
int skip = (pattern.length <= length) ? (offset + length - pattern.length) : offset;
while (skip < offset + length)
@ -136,10 +131,8 @@ public class SearchPattern
return (offset + length - skip);
}
if (skip + pattern.length - 1 < data.length)
skip += table[0xff & data[skip + pattern.length - 1]];
else
skip++;
// We can't use the pre-processed table as we are not matching on the full pattern.
skip++;
}
return 0;
@ -157,10 +150,9 @@ public class SearchPattern
*/
public int startsWith(byte[] data, int offset, int length, int matched)
{
validate(data, offset, length);
validateArgs(data, offset, length);
int matchedCount = 0;
for (int i = 0; i < pattern.length - matched && i < length; i++)
{
if (data[offset + i] == pattern[i + matched])
@ -180,7 +172,7 @@ public class SearchPattern
* @param offset The offset within the data to start the search
* @param length The length of the data to search
*/
private void validate(byte[] data, int offset, int length)
private void validateArgs(byte[] data, int offset, int length)
{
if (offset < 0)
throw new IllegalArgumentException("offset was negative");

View File

@ -32,10 +32,10 @@ public class SearchPatternTest
@Test
public void testBasicSearch()
{
byte[] p1 = new String("truth").getBytes(StandardCharsets.US_ASCII);
byte[] p2 = new String("evident").getBytes(StandardCharsets.US_ASCII);
byte[] p3 = new String("we").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String("we hold these truths to be self evident").getBytes(StandardCharsets.US_ASCII);
byte[] p1 = "truth".getBytes(StandardCharsets.US_ASCII);
byte[] p2 = "evident".getBytes(StandardCharsets.US_ASCII);
byte[] p3 = "we".getBytes(StandardCharsets.US_ASCII);
byte[] d = "we hold these truths to be self evident".getBytes(StandardCharsets.US_ASCII);
// Testing Compiled Pattern p1 "truth"
SearchPattern sp1 = SearchPattern.compile(p1);
@ -65,8 +65,8 @@ public class SearchPatternTest
@Test
public void testDoubleMatch()
{
byte[] p = new String("violent").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String("These violent delights have violent ends.").getBytes(StandardCharsets.US_ASCII);
byte[] p = "violent".getBytes(StandardCharsets.US_ASCII);
byte[] d = "These violent delights have violent ends.".getBytes(StandardCharsets.US_ASCII);
SearchPattern sp = SearchPattern.compile(p);
assertEquals(6, sp.match(d, 0, d.length));
assertEquals(-1, sp.match(d, 6, p.length - 1));
@ -113,8 +113,8 @@ public class SearchPatternTest
@Test
public void testAlmostMatch()
{
byte[] p = new String("violent").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String("vio lent violen v iolent violin vioviolenlent viiolent").getBytes(StandardCharsets.US_ASCII);
byte[] p = "violent".getBytes(StandardCharsets.US_ASCII);
byte[] d = "vio lent violen v iolent violin vioviolenlent viiolent".getBytes(StandardCharsets.US_ASCII);
SearchPattern sp = SearchPattern.compile(p);
assertEquals(-1, sp.match(d, 0, d.length));
}
@ -123,14 +123,14 @@ public class SearchPatternTest
public void testOddSizedPatterns()
{
// Test Large Pattern
byte[] p = new String("pneumonoultramicroscopicsilicovolcanoconiosis").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String("pneumon").getBytes(StandardCharsets.US_ASCII);
byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII);
byte[] d = "pneumon".getBytes(StandardCharsets.US_ASCII);
SearchPattern sp = SearchPattern.compile(p);
assertEquals(-1, sp.match(d, 0, d.length));
// Test Single Character Pattern
p = new String("s").getBytes(StandardCharsets.US_ASCII);
d = new String("the cake is a lie").getBytes(StandardCharsets.US_ASCII);
p = "s".getBytes(StandardCharsets.US_ASCII);
d = "the cake is a lie".getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(10, sp.match(d, 0, d.length));
}
@ -138,30 +138,36 @@ public class SearchPatternTest
@Test
public void testEndsWith()
{
byte[] p = new String("pneumonoultramicroscopicsilicovolcanoconiosis").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String("pneumonoultrami").getBytes(StandardCharsets.US_ASCII);
byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII);
byte[] d = "pneumonoultrami".getBytes(StandardCharsets.US_ASCII);
SearchPattern sp = SearchPattern.compile(p);
assertEquals(15, sp.endsWith(d, 0, d.length));
p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
d = new String("abcdefghijklmnopqrstuvwxyzabcdefghijklmno").getBytes(StandardCharsets.US_ASCII);
p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmno".getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(0, sp.match(d, 0, d.length));
assertEquals(-1, sp.match(d, 1, d.length - 1));
assertEquals(15, sp.endsWith(d, 0, d.length));
p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
d = new String("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(0, sp.match(d, 0, d.length));
assertEquals(26, sp.match(d, 1, d.length - 1));
assertEquals(26, sp.endsWith(d, 0, d.length));
//test no match
p = new String("hello world").getBytes(StandardCharsets.US_ASCII);
d = new String("there is definitely no match in here").getBytes(StandardCharsets.US_ASCII);
p = "hello world".getBytes(StandardCharsets.US_ASCII);
d = "there is definitely no match in here".getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(0, sp.endsWith(d, 0, d.length));
//Test with range on array.
p = "abcde".getBytes(StandardCharsets.US_ASCII);
d = "?abc00000".getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(3, sp.endsWith(d, 0, 4));
}
@Test
@ -178,39 +184,54 @@ public class SearchPatternTest
private void testStartsWith(String offset)
{
byte[] p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
byte[] d = new String(offset + "ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
byte[] p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
byte[] d = (offset + "ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
SearchPattern sp = SearchPattern.compile(p);
assertEquals(18 + offset.length(), sp.match(d, offset.length(), d.length - offset.length()));
assertEquals(-1, sp.match(d, offset.length() + 19, d.length - 19 - offset.length()));
assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 8));
p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
d = new String(offset + "ijklmnopqrstuvwxyNOMATCH").getBytes(StandardCharsets.US_ASCII);
p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
d = (offset + "ijklmnopqrstuvwxyNOMATCH").getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 8));
p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
d = new String(offset + "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
d = (offset + "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 0));
//test no match
p = new String("hello world").getBytes(StandardCharsets.US_ASCII);
d = new String(offset + "there is definitely no match in here").getBytes(StandardCharsets.US_ASCII);
p = "hello world".getBytes(StandardCharsets.US_ASCII);
d = (offset + "there is definitely no match in here").getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 0));
//test large pattern small buffer
p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII);
d = new String(offset + "mnopqrs").getBytes(StandardCharsets.US_ASCII);
p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII);
d = (offset + "mnopqrs").getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(19, sp.startsWith(d, offset.length(), d.length - offset.length(), 12));
//partial pattern
p = new String("abcdef").getBytes(StandardCharsets.US_ASCII);
d = new String(offset + "cde").getBytes(StandardCharsets.US_ASCII);
p = "abcdef".getBytes(StandardCharsets.US_ASCII);
d = (offset + "cde").getBytes(StandardCharsets.US_ASCII);
sp = SearchPattern.compile(p);
assertEquals(5, sp.startsWith(d, offset.length(), d.length - offset.length(), 2));
}
@Test
public void testExampleFrom4673()
{
SearchPattern pattern = SearchPattern.compile("\r\n------WebKitFormBoundaryhXfFAMfUnUKhmqT8".getBytes(StandardCharsets.US_ASCII));
byte[] data = new byte[]{118,97,108,117,101,49,
'\r','\n','-','-','-','-',
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0};
int length = 12;
int partialMatch = pattern.endsWith(data, 0, length);
System.err.println("match1: " + partialMatch);
}
}