package test.codec.http2.model;
import com.firefly.codec.http2.model.MultiPartInputStreamParser;
import com.firefly.utils.codec.B64Code;
import com.firefly.utils.io.IO;
import org.junit.Test;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import java.io.*;
import java.util.Collection;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* MultiPartInputStreamTest
*/
public class MultiPartInputStreamTest {
private static final String FILENAME = "stuff.txt";
protected String _contentType = "multipart/form-data, boundary=AaB03x";
protected String _multi = createMultipartRequestString(FILENAME);
protected String _dirname = System.getProperty("java.io.tmpdir") + File.separator + "myfiles-" + System.currentTimeMillis();
protected File _tmpDir = new File(_dirname);
public MultiPartInputStreamTest() {
_tmpDir.deleteOnExit();
}
@Test
public void testBadMultiPartRequest()
throws Exception {
String boundary = "X0Y0";
String str = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n" +
"How now brown cow." +
"\r\n--" + boundary + "-\r\n\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary=" + boundary,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try {
mpis.getParts();
fail("Multipart incomplete");
} catch (IOException e) {
assertTrue(e.getMessage().startsWith("Incomplete"));
}
}
@Test
public void testFinalBoundaryOnly()
throws Exception {
String delimiter = "\r\n";
final String boundary = "MockMultiPartTestBoundary";
// Malformed multipart request body containing only an arbitrary string of text, followed by the final boundary marker, delimited by empty lines.
String str =
delimiter +
"Hello world" +
delimiter + // Two delimiter markers, which make an empty line.
delimiter +
"--" + boundary + "--" + delimiter;
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary=" + boundary,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertTrue(mpis.getParts().isEmpty());
}
@Test
public void testEmpty()
throws Exception {
String delimiter = "\r\n";
final String boundary = "MockMultiPartTestBoundary";
String str =
delimiter +
"--" + boundary + "--" + delimiter;
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary=" + boundary,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty());
}
@Test
public void testNoBoundaryRequest()
throws Exception {
String str = "--\r\n" +
"Content-Disposition: form-data; name=\"fileName\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"abc\r\n" +
"--\r\n" +
"Content-Disposition: form-data; name=\"desc\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"123\r\n" +
"--\r\n" +
"Content-Disposition: form-data; name=\"title\"\r\n" +
"Content-Type: text/plain; charset=US-ASCII\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"ttt\r\n" +
"--\r\n" +
"Content-Disposition: form-data; name=\"datafile5239138112980980385.txt\"; filename=\"datafile5239138112980980385.txt\"\r\n" +
"Content-Type: application/octet-stream; charset=ISO-8859-1\r\n" +
"Content-Transfer-Encoding: binary\r\n" +
"\r\n" +
"000\r\n" +
"----\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data",
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(4));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Part fileName = mpis.getPart("fileName");
assertThat(fileName, notNullValue());
assertThat(fileName.getSize(), is(3L));
IO.copy(fileName.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("abc"));
baos = new ByteArrayOutputStream();
Part desc = mpis.getPart("desc");
assertThat(desc, notNullValue());
assertThat(desc.getSize(), is(3L));
IO.copy(desc.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("123"));
baos = new ByteArrayOutputStream();
Part title = mpis.getPart("title");
assertThat(title, notNullValue());
assertThat(title.getSize(), is(3L));
IO.copy(title.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("ttt"));
}
@Test
public void testNonMultiPartRequest()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
"Content-type: text/plain",
config,
_tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty());
}
@Test
public void testNoBody()
throws Exception {
String body = "";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try {
mpis.getParts();
fail("Multipart missing body");
} catch (IOException e) {
assertTrue(e.getMessage().startsWith("Missing content"));
}
}
@Test
public void testBodyAlreadyConsumed()
throws Exception {
ServletInputStream is = new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return 0;
}
};
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(is,
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertEquals(0, parts.size());
}
@Test
public void testWhitespaceBodyWithCRLF()
throws Exception {
String whitespace = " \n\n\n\r\n\r\n\r\n\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try {
mpis.getParts();
fail("Multipart missing body");
} catch (IOException e) {
assertTrue(e.getMessage().startsWith("Missing initial"));
}
}
@Test
public void testWhitespaceBody()
throws Exception {
String whitespace = " ";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try {
mpis.getParts();
fail("Multipart missing body");
} catch (IOException e) {
assertTrue(e.getMessage().startsWith("Missing initial"));
}
}
@Test
public void testLeadingWhitespaceBodyWithCRLF()
throws Exception {
String body = " \n\n\n\r\n\r\n\r\n\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n" +
"\r\n" +
"Joe Blow\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "aaaa" +
"bbbbb" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts, notNullValue());
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1");
assertThat(field1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(field1.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("Joe Blow"));
Part stuff = mpis.getPart("stuff");
assertThat(stuff, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("aaaa"));
}
@Test
public void testLeadingWhitespaceBodyWithoutCRLF()
throws Exception {
String body = " " +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"\r\n" +
"\r\n" +
"Joe Blow\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "aaaa" +
"bbbbb" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts, notNullValue());
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1");
assertThat(field1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(field1.getInputStream(), baos);
assertThat(baos.toString("US-ASCII"), is("Joe Blow"));
Part stuff = mpis.getPart("stuff");
assertThat(stuff, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(stuff.getInputStream(), baos);
assertTrue(baos.toString("US-ASCII").contains("bbbbb"));
}
@Test
public void testNoLimits()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertFalse(parts.isEmpty());
}
@Test
public void testRequestTooBig()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try {
parts = mpis.getParts();
fail("Request should have exceeded maxRequestSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Request exceeds maxRequestSize"));
}
}
@Test
public void testRequestTooBigThrowsErrorOnGetParts()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
//cause parsing
try {
parts = mpis.getParts();
fail("Request should have exceeded maxRequestSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Request exceeds maxRequestSize"));
}
//try again
try {
parts = mpis.getParts();
fail("Request should have exceeded maxRequestSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Request exceeds maxRequestSize"));
}
}
@Test
public void testFileTooBig()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try {
parts = mpis.getParts();
fail("stuff.txt should have been larger than maxFileSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Multipart Mime part"));
}
}
@Test
public void testFileTooBigThrowsErrorOnGetParts()
throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try {
parts = mpis.getParts(); //caused parsing
fail("stuff.txt should have been larger than maxFileSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Multipart Mime part"));
}
//test again after the parsing
try {
parts = mpis.getParts(); //caused parsing
fail("stuff.txt should have been larger than maxFileSize");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().startsWith("Multipart Mime part"));
}
}
@Test
public void testPartFileNotDeleted() throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPartInputStreamParser.MultiPart part = (MultiPartInputStreamParser.MultiPart) mpis.getPart("stuff");
File stuff = ((MultiPartInputStreamParser.MultiPart) part).getFile();
assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file
part.write("tptfd.txt");
File tptfd = new File(_dirname + File.separator + "tptfd.txt");
assertThat(tptfd.exists(), is(true));
assertThat(stuff.exists(), is(false)); //got renamed
part.cleanUp();
assertThat(tptfd.exists(), is(true)); //explicitly written file did not get removed after cleanup
tptfd.deleteOnExit(); //clean up test
}
@Test
public void testPartTmpFileDeletion() throws Exception {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPartInputStreamParser.MultiPart part = (MultiPartInputStreamParser.MultiPart) mpis.getPart("stuff");
File stuff = ((MultiPartInputStreamParser.MultiPart) part).getFile();
assertThat(stuff, notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(stuff.exists(), is(true));
part.cleanUp();
assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup
}
@Test
public void testLFOnlyRequest()
throws Exception {
String str = "--AaB03x\n" +
"content-disposition: form-data; name=\"field1\"\n" +
"\n" +
"Joe Blow\n" +
"--AaB03x\n" +
"content-disposition: form-data; name=\"field2\"\n" +
"\n" +
"Other\n" +
"--AaB03x--\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Joe Blow"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
@Test
public void testCROnlyRequest()
throws Exception {
String str = "--AaB03x\r" +
"content-disposition: form-data; name=\"field1\"\r" +
"\r" +
"Joe Blow\r" +
"--AaB03x\r" +
"content-disposition: form-data; name=\"field2\"\r" +
"\r" +
"Other\r" +
"--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Joe Blow"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
@Test
public void testCRandLFMixRequest()
throws Exception {
String str = "--AaB03x\r" +
"content-disposition: form-data; name=\"field1\"\r" +
"\r" +
"\nJoe Blow\n" +
"\r" +
"--AaB03x\r" +
"content-disposition: form-data; name=\"field2\"\r" +
"\r" +
"Other\r" +
"--AaB03x--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part p1 = mpis.getPart("field1");
assertThat(p1, notNullValue());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("\nJoe Blow\n"));
Part p2 = mpis.getPart("field2");
assertThat(p2, notNullValue());
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertThat(baos.toString("UTF-8"), is("Other"));
}
@Test
public void testBufferOverflowNoCRLF() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("--AaB03x".getBytes());
for (int i = 0; i < 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
{
baos.write('a');
}
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try {
mpis.getParts();
fail("Multipart buffer overrun");
} catch (IOException e) {
assertTrue(e.getMessage().startsWith("Buffer size exceeded"));
}
}
@Test
public void testCharsetEncoding() throws Exception {
String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1";
String str = "--TheBoundary\r" +
"content-disposition: form-data; name=\"field1\"\r" +
"\r" +
"\nJoe Blow\n" +
"\r" +
"--TheBoundary--\r";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
}
@Test
public void testBadlyEncodedFilename() throws Exception {
String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "Taken on Aug 22 \\ 2012.jpg" + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "stuff" +
"aaa" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart) parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
}
@Test
public void testBadlyEncodedMSFilename() throws Exception {
String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "stuff" +
"aaa" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart) parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
@Test
public void testCorrectlyEncodedMSFilename() throws Exception {
String contents = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + "c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "stuff" +
"aaa" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
assertThat(((MultiPartInputStreamParser.MultiPart) parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
public void testMulti()
throws Exception {
testMulti(FILENAME);
}
@Test
public void testMultiWithSpaceInFilename() throws Exception {
testMulti("stuff with spaces.txt");
}
@Test
public void testWriteFilesIfContentDispositionFilename()
throws Exception {
String s = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" +
"\r\n" +
"Joe Blow\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + "sss" +
"aaa" + "\r\n" +
"--AaB03x--\r\n";
//all default values for multipartconfig, ie file size threshold 0
MultipartConfigElement config = new MultipartConfigElement(_dirname);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(s.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
mpis.setWriteFilesWithFilenames(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); //has a filename, should be written to a file
File f = ((MultiPartInputStreamParser.MultiPart) field1).getFile();
assertThat(f, notNullValue()); // longer than 100 bytes, should already be a tmp file
Part stuff = mpis.getPart("stuff");
f = ((MultiPartInputStreamParser.MultiPart) stuff).getFile(); //should only be in memory, no filename
assertThat(f, nullValue());
}
private void testMulti(String filename) throws IOException, ServletException, InterruptedException {
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer
assertThat(field1, notNullValue());
assertThat(field1.getName(), is("field1"));
InputStream is = field1.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
IO.copy(is, os);
assertEquals("Joe Blow", new String(os.toByteArray()));
assertEquals(8, field1.getSize());
assertNotNull(((MultiPartInputStreamParser.MultiPart) field1).getBytes());//in internal buffer
field1.write("field1.txt");
assertNull(((MultiPartInputStreamParser.MultiPart) field1).getBytes());//no longer in internal buffer
File f = new File(_dirname + File.separator + "field1.txt");
assertTrue(f.exists());
field1.write("another_field1.txt"); //write after having already written
File f2 = new File(_dirname + File.separator + "another_field1.txt");
assertTrue(f2.exists());
assertFalse(f.exists()); //should have been renamed
field1.delete(); //file should be deleted
assertFalse(f.exists()); //original file was renamed
assertFalse(f2.exists()); //2nd written file was explicitly deleted
MultiPartInputStreamParser.MultiPart stuff = (MultiPartInputStreamParser.MultiPart) mpis.getPart("stuff");
assertThat(stuff.getSubmittedFileName(), is(filename));
assertThat(stuff.getContentType(), is("text/plain"));
assertThat(stuff.getHeader("Content-Type"), is("text/plain"));
assertThat(stuff.getHeaders("content-type").size(), is(1));
assertThat(stuff.getHeader("content-disposition"), is("form-data; name=\"stuff\"; filename=\"" + filename + "\""));
assertThat(stuff.getHeaderNames().size(), is(2));
assertThat(stuff.getSize(), is(51L));
File tmpfile = ((MultiPartInputStreamParser.MultiPart) stuff).getFile();
assertThat(tmpfile, notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(((MultiPartInputStreamParser.MultiPart) stuff).getBytes(), nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(), is(true));
assertThat(tmpfile.getName(), is(not("stuff with space.txt")));
stuff.write(filename);
f = new File(_dirname + File.separator + filename);
assertThat(f.exists(), is(true));
assertThat(tmpfile.exists(), is(false));
try {
stuff.getInputStream();
} catch (Exception e) {
fail("Part.getInputStream() after file rename operation");
}
f.deleteOnExit(); //clean up after test
}
@Test
public void testMultiSameNames()
throws Exception {
String sameNames = "--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff1.txt\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"00000\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"110000000000000000000000000000000000000000000000000\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(sameNames.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertEquals(2, parts.size());
for (Part p : parts)
assertEquals("stuff", p.getName());
//if they all have the name name, then only retrieve the first one
Part p = mpis.getPart("stuff");
assertNotNull(p);
assertEquals(5, p.getSize());
}
@Test
public void testBase64EncodedContent() throws Exception {
String contentWithEncodedPart =
"--AaB03x\r\n" +
"Content-disposition: form-data; name=\"other\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"other" + "\r\n" +
"--AaB03x\r\n" +
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
B64Code.encode("hello jetty") + "\r\n" +
"--AaB03x\r\n" +
"Content-disposition: form-data; name=\"final\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"the end" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertEquals(3, parts.size());
Part p1 = mpis.getPart("other");
assertNotNull(p1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertEquals("other", baos.toString("US-ASCII"));
Part p2 = mpis.getPart("stuff");
assertNotNull(p2);
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertEquals("hello jetty", baos.toString("US-ASCII"));
Part p3 = mpis.getPart("final");
assertNotNull(p3);
baos = new ByteArrayOutputStream();
IO.copy(p3.getInputStream(), baos);
assertEquals("the end", baos.toString("US-ASCII"));
}
@Test
public void testQuotedPrintableEncoding() throws Exception {
String contentWithEncodedPart =
"--AaB03x\r\n" +
"Content-disposition: form-data; name=\"other\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"other" + "\r\n" +
"--AaB03x\r\n" +
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"truth=3Dbeauty" + "\r\n" +
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertEquals(2, parts.size());
Part p1 = mpis.getPart("other");
assertNotNull(p1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IO.copy(p1.getInputStream(), baos);
assertEquals("other", baos.toString("US-ASCII"));
Part p2 = mpis.getPart("stuff");
assertNotNull(p2);
baos = new ByteArrayOutputStream();
IO.copy(p2.getInputStream(), baos);
assertEquals("truth=beauty", baos.toString("US-ASCII"));
}
private String createMultipartRequestString(String filename) {
int length = filename.length();
String name = filename;
if (length > 10)
name = filename.substring(0, 10);
StringBuffer filler = new StringBuffer();
int i = name.length();
while (i < 51) {
filler.append("0");
i++;
}
return "--AaB03x\r\n" +
"content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n" +
"\r\n" +
"Joe Blow\r\n" +
"--AaB03x\r\n" +
"content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" + name +
filler.toString() + "\r\n" +
"--AaB03x--\r\n";
}
}