blob: 95ab4110069bfbd41ab85f6c9f6d1f3b4004a9ed [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.DispatcherType;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class RequestTest
{
private static final Logger LOG = Log.getLogger(RequestTest.class);
private Server _server;
private LocalConnector _connector;
private RequestHandler _handler;
@Before
public void init() throws Exception
{
_server = new Server();
HttpConnectionFactory http = new HttpConnectionFactory();
http.setInputBufferSize(1024);
http.getHttpConfiguration().setRequestHeaderSize(512);
http.getHttpConfiguration().setResponseHeaderSize(512);
http.getHttpConfiguration().setOutputBufferSize(2048);
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
_connector = new LocalConnector(_server,http);
_server.addConnector(_connector);
_handler = new RequestHandler();
_server.setHandler(_handler);
ErrorHandler errors = new ErrorHandler();
errors.setShowStacks(true);
_server.addBean(errors);
_server.start();
}
@After
public void destroy() throws Exception
{
_server.stop();
_server.join();
}
@Test
public void testParamExtraction() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
try
{
Map<String, String[]> map = null;
// do the parse
map = request.getParameterMap();
return false;
}
catch(BadMessageException e)
{
// Should be able to retrieve the raw query
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%ZZbbb&other=value");
}
}
};
//Send a request with query string with illegal hex code to cause
//an exception parsing the params
String request="GET /?param=aaa%ZZbbb&other=value HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/html;charset=utf8\n"+
"Connection: close\n"+
"\n";
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testEmptyHeaders() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
assertNotNull(request.getLocale());
assertTrue(request.getLocales().hasMoreElements()); // Default locale
assertEquals("",request.getContentType());
assertNull(request.getCharacterEncoding());
assertEquals(0,request.getQueryString().length());
assertEquals(-1,request.getContentLength());
assertNull(request.getCookies());
assertEquals("",request.getHeader("Name"));
assertTrue(request.getHeaders("Name").hasMoreElements()); // empty
try
{
request.getDateHeader("Name");
assertTrue(false);
}
catch(IllegalArgumentException e)
{
}
assertEquals(-1,request.getDateHeader("Other"));
return true;
}
};
String request="GET /? HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Connection: close\n"+
"Content-Type: \n"+
"Accept-Language: \n"+
"Cookie: \n"+
"Name: \n"+
"\n";
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testMultiPartNoConfig() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
try
{
Part foo = request.getPart("stuff");
return false;
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
return true;
}
catch (Exception e)
{
return false;
}
}
};
String multipart = "--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\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
String request="GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
multipart;
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testLocale() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
assertThat(request.getLocale().getLanguage(),is("da"));
Enumeration<Locale> locales = request.getLocales();
Locale locale=locales.nextElement();
assertThat(locale.getLanguage(),is("da"));
assertThat(locale.getCountry(),is(""));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is("AU"));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is("GB"));
locale=locales.nextElement();
assertThat(locale.getLanguage(),is("en"));
assertThat(locale.getCountry(),is(""));
assertFalse(locales.hasMoreElements());
return true;
}
};
String request="GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Connection: close\r\n"+
"Accept-Language: da, en-gb;q=0.8, en;q=0.7\r\n"+
"Accept-Language: XX;q=0, en-au;q=0.9\r\n"+
"\r\n";
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
public void testMultiPart() throws Exception
{
final File testTmpDir = File.createTempFile("reqtest", null);
if (testTmpDir.exists())
testTmpDir.delete();
testTmpDir.mkdir();
testTmpDir.deleteOnExit();
assertTrue(testTmpDir.list().length == 0);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/foo");
contextHandler.setResourceBase(".");
contextHandler.setHandler(new MultiPartRequestHandler(testTmpDir));
contextHandler.addEventListener(new MultiPartCleanerListener()
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
MultiPartInputStreamParser m = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m);
assertNotNull (c);
assertTrue(c == sre.getServletContext());
assertTrue(!m.getParsedParts().isEmpty());
assertTrue(testTmpDir.list().length == 2);
super.requestDestroyed(sre);
String[] files = testTmpDir.list();
assertTrue(files.length == 0);
}
});
_server.stop();
_server.setHandler(contextHandler);
_server.start();
String multipart = "--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.upload\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
String request="GET /foo/x.html HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
multipart;
String responses=_connector.getResponses(request);
// System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testBadMultiPart() throws Exception
{
//a bad multipart where one of the fields has no name
final File testTmpDir = File.createTempFile("badmptest", null);
if (testTmpDir.exists())
testTmpDir.delete();
testTmpDir.mkdir();
testTmpDir.deleteOnExit();
assertTrue(testTmpDir.list().length == 0);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/foo");
contextHandler.setResourceBase(".");
contextHandler.setHandler(new BadMultiPartRequestHandler(testTmpDir));
contextHandler.addEventListener(new MultiPartCleanerListener()
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
MultiPartInputStreamParser m = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m);
assertNotNull (c);
assertTrue(c == sre.getServletContext());
super.requestDestroyed(sre);
String[] files = testTmpDir.list();
assertTrue(files.length == 0);
}
});
_server.stop();
_server.setHandler(contextHandler);
_server.start();
String multipart = "--AaB03x\r\n"+
"content-disposition: form-data; name=\"xxx\"\r\n"+
"\r\n"+
"Joe Blow\r\n"+
"--AaB03x\r\n"+
"content-disposition: form-data; filename=\"foo.upload\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
String request="GET /foo/x.html HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
multipart;
try(StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
String responses=_connector.getResponses(request);
//System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 500"));
}
}
@Test
public void testBadUtf8ParamExtraction() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
try
{
// This throws an exception if attempted
request.getParameter("param");
return false;
}
catch(BadMessageException e)
{
// Should still be able to get the raw query.
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%E7bbb");
}
}
};
//Send a request with query string with illegal hex code to cause
//an exception parsing the params
String request="GET /?param=aaa%E7bbb HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/html;charset=utf8\n"+
"Connection: close\n"+
"\n";
LOG.info("Expecting NotUtf8Exception in state 36...");
String responses=_connector.getResponses(request);
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testInvalidHostHeader() throws Exception
{
// Use a contextHandler with vhosts to force call to Request.getServerName()
ContextHandler context = new ContextHandler();
context.addVirtualHosts(new String[]{"something"});
_server.stop();
_server.setHandler(context);
_server.start();
// Request with illegal Host header
String request="GET / HTTP/1.1\n"+
"Host: whatever.com:xxxx\n"+
"Content-Type: text/html;charset=utf8\n"+
"Connection: close\n"+
"\n";
String responses=_connector.getResponses(request);
assertThat(responses, Matchers.startsWith("HTTP/1.1 400"));
}
@Test
public void testContentTypeEncoding() throws Exception
{
final ArrayList<String> results = new ArrayList<>();
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
results.add(request.getContentType());
results.add(request.getCharacterEncoding());
return true;
}
};
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Content-Type: text/test\n"+
"\n"+
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Content-Type: text/html;charset=utf8\n"+
"\n"+
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Content-Type: text/html; charset=\"utf8\"\n"+
"\n"+
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Content-Type: text/html; other=foo ; blah=\"charset=wrong;\" ; charset = \" x=z; \" ; more=values \n"+
"Connection: close\n"+
"\n"
);
int i=0;
assertEquals("text/test",results.get(i++));
assertEquals(null,results.get(i++));
assertEquals("text/html;charset=utf8",results.get(i++));
assertEquals("utf-8",results.get(i++));
assertEquals("text/html; charset=\"utf8\"",results.get(i++));
assertEquals("utf-8",results.get(i++));
assertTrue(results.get(i++).startsWith("text/html"));
assertEquals(" x=z; ",results.get(i++));
}
@Test
public void testHostPort() throws Exception
{
final ArrayList<String> results = new ArrayList<>();
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
results.add(request.getRequestURL().toString());
results.add(request.getRemoteAddr());
results.add(request.getServerName());
results.add(String.valueOf(request.getServerPort()));
return true;
}
};
results.clear();
String response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Connection: close\n"+
"\n");
int i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost:8888\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET http://myhost:8888/ HTTP/1.0\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET http://myhost:8888/ HTTP/1.1\n"+
"Host: wrong:666\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4:8888\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
"Connection: close\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("443",results.get(i++));
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
"\n");
i=0;
assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("8888",results.get(i++));
}
@Test
public void testContent() throws Exception
{
final AtomicInteger length=new AtomicInteger();
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
int len=request.getContentLength();
ServletInputStream in = request.getInputStream();
for (int i=0;i<len;i++)
{
int b=in.read();
if (b<0)
return false;
}
if (in.read()>0)
return false;
length.set(len);
return true;
}
};
String content="";
for (int l=0;l<1024;l++)
{
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data-test\r\n"+
"Content-Length: "+l+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
Log.getRootLogger().debug("test l={}",l);
String response = _connector.getResponses(request);
Log.getRootLogger().debug(response);
assertThat(response, containsString(" 200 OK"));
assertEquals(l,length.get());
content+="x";
}
}
@Test
public void testEncodedForm() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
String actual = request.getParameter("name2");
return "test2".equals(actual);
}
};
String content="name1=test&name2=test2&name3=&name4=test";
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
"Content-Length: "+content.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
public void testEncodedFormUnknownMethod() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
return request.getParameter("name1")==null && request.getParameter("name2")==null && request.getParameter("name3")==null;
}
};
String content="name1=test&name2=test2&name3=&name4=test";
String request="UNKNOWN / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
"Content-Length: "+content.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
public void testEncodedFormExtraMethod() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
String actual = request.getParameter("name2");
return "test2".equals(actual);
}
};
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().addFormEncodedMethod("Extra");
String content="name1=test&name2=test2&name3=&name4=test";
String request="EXTRA / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
"Content-Length: "+content.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
public void test8859EncodedForm() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
// Should be "testä"
// "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
request.setCharacterEncoding(StandardCharsets.ISO_8859_1.name());
String actual = request.getParameter("name2");
return "test\u00e4".equals(actual);
}
};
String content="name1=test&name2=test%E4&name3=&name4=test";
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
"Content-Length: "+content.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
public void testUTF8EncodedForm() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
// http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex
// Should be "testä"
// "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
String actual = request.getParameter("name2");
return "test\u00e4".equals(actual);
}
};
String content="name1=test&name2=test%C3%A4&name3=&name4=test";
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
"Content-Length: "+content.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
content;
String response = _connector.getResponses(request);
assertThat(response, containsString(" 200 OK"));
}
@Test
@Ignore("See issue #1175")
public void testMultiPartFormDataReadInputThenParams() throws Exception
{
final File tmpdir = MavenTestingUtils.getTargetTestingDir("multipart");
FS.ensureEmpty(tmpdir);
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
if (baseRequest.getDispatcherType() != DispatcherType.REQUEST)
return;
// Fake a MultiPartConfig'd servlet endpoint
MultipartConfigElement multipartConfig = new MultipartConfigElement(tmpdir.getAbsolutePath());
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, multipartConfig);
// Normal processing
baseRequest.setHandled(true);
// Fake the commons-fileupload behavior
int length = request.getContentLength();
InputStream in = request.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
IO.copy(in, out, length); // commons-fileupload does not read to EOF
LOG.info("input stream = " + in);
// Record what happened as servlet response headers
response.setIntHeader("x-request-content-length", request.getContentLength());
response.setIntHeader("x-request-content-read", out.size());
String foo = request.getParameter("foo"); // uri query parameter
String bar = request.getParameter("bar"); // form-data content parameter
response.setHeader("x-foo", foo == null ? "null" : foo);
response.setHeader("x-bar", bar == null ? "null" : bar);
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String multipart = "--AaBbCc\r\n"+
"content-disposition: form-data; name=\"bar\"\r\n"+
"\r\n"+
"BarContent\r\n"+
"--AaBbCc\r\n"+
"content-disposition: form-data; name=\"stuff\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaBbCc--\r\n";
String request="POST /?foo=FooUri HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaBbCc\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
multipart;
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request));
// It should always be possible to read query string
assertThat("response.x-foo", response.get("x-foo"), is("FooUri"));
// Not possible to read request content parameters?
assertThat("response.x-bar", response.get("x-bar"), is("null")); // TODO: should this work?
}
@Test
public void testPartialRead() throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
baseRequest.setHandled(true);
Reader reader=request.getReader();
byte[] b=("read="+reader.read()+"\n").getBytes(StandardCharsets.UTF_8);
response.setContentLength(b.length);
response.getOutputStream().write(b);
response.flushBuffer();
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String request="GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/plane\r\n"+
"Content-Length: "+10+"\r\n"+
"\r\n"+
"0123456789\r\n"+
"GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/plane\r\n"+
"Content-Length: "+10+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
"ABCDEFGHIJ\r\n";
String responses = _connector.getResponses(request);
int index=responses.indexOf("read="+(int)'0');
assertTrue(index>0);
index=responses.indexOf("read="+(int)'A',index+7);
assertTrue(index>0);
}
@Test
public void testQueryAfterRead()
throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
baseRequest.setHandled(true);
Reader reader=request.getReader();
String in = IO.toString(reader);
String param = request.getParameter("param");
byte[] b=("read='"+in+"' param="+param+"\n").getBytes(StandardCharsets.UTF_8);
response.setContentLength(b.length);
response.getOutputStream().write(b);
response.flushBuffer();
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String request="POST /?param=right HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: application/x-www-form-urlencoded\r\n"+
"Content-Length: "+11+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
"param=wrong\r\n";
String responses = _connector.getResponses(request);
assertTrue(responses.indexOf("read='param=wrong' param=right")>0);
}
@Test
public void testSessionAfterRedirect() throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
baseRequest.setHandled(true);
response.sendRedirect("/foo");
try
{
request.getSession(true);
fail("Session should not be created after response committed");
}
catch (IllegalStateException e)
{
//expected
}
catch (Exception e)
{
fail("Session creation after response commit should throw IllegalStateException");
}
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String response=_connector.getResponses("GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Connection: close\n"+
"\n");
assertThat(response, containsString(" 302 Found"));
assertThat(response, containsString("Location: http://myhost/foo"));
}
@Test
public void testPartialInput() throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
baseRequest.setHandled(true);
InputStream in=request.getInputStream();
byte[] b=("read="+in.read()+"\n").getBytes(StandardCharsets.UTF_8);
response.setContentLength(b.length);
response.getOutputStream().write(b);
response.flushBuffer();
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String request="GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/plane\r\n"+
"Content-Length: "+10+"\r\n"+
"\r\n"+
"0123456789\r\n"+
"GET / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/plane\r\n"+
"Content-Length: "+10+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
"ABCDEFGHIJ\r\n";
String responses = _connector.getResponses(request);
int index=responses.indexOf("read="+(int)'0');
assertTrue(index>0);
index=responses.indexOf("read="+(int)'A',index+7);
assertTrue(index>0);
}
@Test
public void testConnectionClose() throws Exception
{
String response;
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
response.getOutputStream().println("Hello World");
return true;
}
};
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Connection: close\n"+
"\n"
);
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Connection: Other, close\n"+
"\n"
);
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
"Host: whatever\n"+
"\n"
);
assertThat(response, containsString("200"));
assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
"Host: whatever\n"+
"Connection: Other, close\n"+
"\n"
);
assertThat(response, containsString("200"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
"Host: whatever\n"+
"Connection: Other,,keep-alive\n"+
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: keep-alive"));
assertThat(response, containsString("Hello World"));
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
response.setHeader("Connection","TE");
response.addHeader("Connection","Other");
response.getOutputStream().println("Hello World");
return true;
}
};
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response, containsString("200"));
assertThat(response, containsString("Connection: TE,Other"));
assertThat(response, containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Connection: close\n"+
"\n"
);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("Connection: close"));
assertThat(response, containsString("Hello World"));
}
@Test
public void testCookies() throws Exception
{
final ArrayList<Cookie> cookies = new ArrayList<>();
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
javax.servlet.http.Cookie[] ca = request.getCookies();
if (ca!=null)
cookies.addAll(Arrays.asList(ca));
response.getOutputStream().println("Hello World");
return true;
}
};
String response;
cookies.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(0,cookies.size());
cookies.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Cookie: name=quoted=\"\\\"value\\\"\"\n" +
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(1,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("quoted=\"value\"", cookies.get(0).getValue());
cookies.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
"Cookie: name=value; other=\"quoted=;value\"\n" +
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(2,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("value", cookies.get(0).getValue());
assertEquals("other", cookies.get(1).getName());
assertEquals("quoted=;value", cookies.get(1).getValue());
cookies.clear();
response=_connector.getResponses(
"GET /other HTTP/1.1\n"+
"Host: whatever\n"+
"Other: header\n"+
"Cookie: name=value; other=\"quoted=;value\"\n" +
"\n"+
"GET /other HTTP/1.1\n"+
"Host: whatever\n"+
"Other: header\n"+
"Cookie: name=value; other=\"quoted=;value\"\n" +
"Connection: close\n"+
"\n"
);
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
assertThat(response.substring(15), containsString("HTTP/1.1 200 OK"));
assertEquals(4,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("value", cookies.get(0).getValue());
assertEquals("other", cookies.get(1).getName());
assertEquals("quoted=;value", cookies.get(1).getValue());
assertSame(cookies.get(0), cookies.get(2));
assertSame(cookies.get(1), cookies.get(3));
cookies.clear();
response=_connector.getResponses(
"GET /other HTTP/1.1\n"+
"Host: whatever\n"+
"Other: header\n"+
"Cookie: name=value; other=\"quoted=;value\"\n" +
"\n"+
"GET /other HTTP/1.1\n"+
"Host: whatever\n"+
"Other: header\n"+
"Cookie: name=value; other=\"othervalue\"\n" +
"Connection: close\n"+
"\n"
);
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
assertThat(response.substring(15), containsString("HTTP/1.1 200 OK"));
assertEquals(4,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("value", cookies.get(0).getValue());
assertEquals("other", cookies.get(1).getName());
assertEquals("quoted=;value", cookies.get(1).getValue());
assertNotSame(cookies.get(0), cookies.get(2));
assertNotSame(cookies.get(1), cookies.get(3));
cookies.clear();
//NOTE: the javax.servlet.http.Cookie class sets the system property org.glassfish.web.rfc2109_cookie_names_enforced
//to TRUE by default, and rejects all cookie names containing punctuation.Therefore this test cannot use "name2".
response=_connector.getResponses(
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name0=value0; name1 = value1 ; \"name2\" = \"\\\"value2\\\"\" \n" +
"Cookie: $Version=2; name3=value3=value3;$path=/path;$domain=acme.com;$port=8080; name4=; name5 = ; name6\n" +
"Cookie: name7=value7;\n" +
"Connection: close\r\n"+
"\r\n");
assertEquals("name0", cookies.get(0).getName());
assertEquals("value0", cookies.get(0).getValue());
assertEquals("name1", cookies.get(1).getName());
assertEquals("value1", cookies.get(1).getValue());
assertEquals("name2", cookies.get(2).getName());
assertEquals("\"value2\"", cookies.get(2).getValue());
assertEquals("name3", cookies.get(3).getName());
assertEquals("value3=value3", cookies.get(3).getValue());
assertEquals(2, cookies.get(3).getVersion());
assertEquals("/path", cookies.get(3).getPath());
assertEquals("acme.com", cookies.get(3).getDomain());
assertEquals("$port=8080", cookies.get(3).getComment());
assertEquals("name4", cookies.get(4).getName());
assertEquals("", cookies.get(4).getValue());
assertEquals("name5", cookies.get(5).getName());
assertEquals("", cookies.get(5).getValue());
assertEquals("name6", cookies.get(6).getName());
assertEquals("", cookies.get(6).getValue());
assertEquals("name7", cookies.get(7).getName());
assertEquals("value7", cookies.get(7).getValue());
cookies.clear();
response=_connector.getResponses(
"GET /other HTTP/1.1\n"+
"Host: whatever\n"+
"Other: header\n"+
"Cookie: __utmz=14316.133020.1.1.utr=gna.de|ucn=(real)|utd=reral|utct=/games/hen-one,gnt-50-ba-keys:key,2072262.html\n"+
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertEquals(1,cookies.size());
assertEquals("__utmz", cookies.get(0).getName());
assertEquals("14316.133020.1.1.utr=gna.de|ucn=(real)|utd=reral|utct=/games/hen-one,gnt-50-ba-keys:key,2072262.html", cookies.get(0).getValue());
}
@Ignore("No longer relevant")
@Test
public void testCookieLeak() throws Exception
{
final String[] cookie=new String[10];
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
for (int i=0;i<cookie.length; i++)
cookie[i]=null;
Cookie[] cookies = request.getCookies();
for (int i=0;cookies!=null && i<cookies.length; i++)
{
cookie[i]=cookies[i].getValue();
}
return true;
}
};
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: other=cookie\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals("value",cookie[0]);
assertEquals(null,cookie[1]);
request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: \r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals(null,cookie[0]);
assertEquals(null,cookie[1]);
request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Cookie: other=cookie\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Cookie:\r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals("value",cookie[0]);
assertEquals(null,cookie[1]);
}
@Test
public void testHashDOSKeys() throws Exception
{
try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
// Expecting maxFormKeys limit and Closing HttpParser exceptions...
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
StringBuilder buf = new StringBuilder(4000000);
buf.append("a=b");
// The evil keys file is not distributed - as it is dangerous
File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
if (evil_keys.exists())
{
// Using real evil keys!
try (BufferedReader in = new BufferedReader(new FileReader(evil_keys)))
{
String key=null;
while((key=in.readLine())!=null)
buf.append("&").append(key).append("=").append("x");
}
}
else
{
// we will just create a lot of keys and make sure the limit is applied
for (int i=0;i<2000;i++)
buf.append("&").append("K").append(i).append("=").append("x");
}
buf.append("&c=d");
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
}
};
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n"+
"Content-Length: "+buf.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
buf;
long start=System.currentTimeMillis();
String rawResponse = _connector.getResponses(request);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis();
assertTrue((now-start)<5000);
}
}
@Test
public void testHashDOSSize() throws Exception
{
try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
LOG.info("Expecting maxFormSize limit and too much data exceptions...");
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",3396);
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
StringBuilder buf = new StringBuilder(4000000);
buf.append("a=b");
// we will just create a lot of keys and make sure the limit is applied
for (int i=0;i<500;i++)
buf.append("&").append("K").append(i).append("=").append("x");
buf.append("&c=d");
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
}
};
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n"+
"Content-Length: "+buf.length()+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
buf;
long start=System.currentTimeMillis();
String rawResponse = _connector.getResponses(request);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis();
assertTrue((now-start)<5000);
}
}
@Test(expected = UnsupportedEncodingException.class)
public void testNotSupportedCharacterEncoding() throws UnsupportedEncodingException
{
Request request = new Request(null, null);
request.setCharacterEncoding("doesNotExist");
}
interface RequestTester
{
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
}
private class RequestHandler extends AbstractHandler
{
private RequestTester _checker;
@SuppressWarnings("unused")
private String _content;
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
if (request.getContentLength()>0
&& !MimeTypes.Type.FORM_ENCODED.asString().equals(request.getContentType())
&& !request.getContentType().startsWith("multipart/form-data"))
_content=IO.toString(request.getInputStream());
if (_checker!=null && _checker.check(request,response))
response.setStatus(200);
else
response.sendError(500);
}
}
private class MultiPartRequestHandler extends AbstractHandler
{
File tmpDir;
public MultiPartRequestHandler(File tmpDir)
{
this.tmpDir = tmpDir;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
try
{
MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
String field1 = request.getParameter("field1");
assertNotNull(field1);
Part foo = request.getPart("stuff");
assertNotNull(foo);
assertTrue(foo.getSize() > 0);
response.setStatus(200);
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
response.setStatus(200);
}
catch (Exception e)
{
response.sendError(500);
}
}
}
private class BadMultiPartRequestHandler extends AbstractHandler
{
File tmpDir;
public BadMultiPartRequestHandler(File tmpDir)
{
this.tmpDir = tmpDir;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
try
{
MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
//We should get an error when we getParams if there was a problem parsing the multipart
request.getPart("xxx");
//A 200 response is actually wrong here
}
catch (RuntimeException e)
{
response.sendError(500);
}
}
}
}