init
This commit is contained in:
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.servlets.WebdavServlet;
|
||||
import org.apache.catalina.startup.TesterServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
/**
|
||||
* Test cases for {@link Connector}.
|
||||
*/
|
||||
public class TestConnector extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testStop() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w =
|
||||
Tomcat.addServlet(root, "tester", new TesterServlet());
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/", "tester");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
|
||||
rc = -1;
|
||||
bc.recycle();
|
||||
|
||||
connector.stop();
|
||||
|
||||
try {
|
||||
rc = getUrl("http://localhost:" + getPort() + "/", bc, 1000,
|
||||
null, null);
|
||||
} catch (SocketTimeoutException ste) {
|
||||
// May also see this with NIO
|
||||
// Make sure the test passes if we do
|
||||
rc = 503;
|
||||
}
|
||||
Assert.assertEquals(503, rc);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPort() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Connector connector1 = tomcat.getConnector();
|
||||
connector1.setPort(0);
|
||||
|
||||
Connector connector2 = new Connector();
|
||||
connector2.setPort(0);
|
||||
|
||||
tomcat.getService().addConnector(connector2);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int localPort1 = connector1.getLocalPort();
|
||||
int localPort2 = connector2.getLocalPort();
|
||||
|
||||
Assert.assertTrue(localPort1 > 0);
|
||||
Assert.assertTrue(localPort2 > 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
private void doTestTrace(Servlet servlet, boolean allowTrace) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context root = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(root, "default", servlet);
|
||||
root.addServletMappingDecoded("/", "default");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
connector.setAllowTrace(allowTrace);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String,List<String>> respHeaders = new HashMap<>();
|
||||
int rc = methodUrl("http://localhost:" + getPort() + "/index.html",
|
||||
bc, 30000, null, respHeaders, "OPTIONS");
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
|
||||
boolean foundTrace = false;
|
||||
for (String header : respHeaders.get("Allow")) {
|
||||
if (header.contains("TRACE")) {
|
||||
foundTrace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowTrace) {
|
||||
Assert.assertTrue(foundTrace);
|
||||
} else {
|
||||
Assert.assertFalse(foundTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.MessageBytes;
|
||||
|
||||
public class TestCoyoteAdapter extends TomcatBaseTest {
|
||||
|
||||
public static final String TEXT_8K;
|
||||
public static final byte[] BYTES_8K;
|
||||
|
||||
static {
|
||||
StringBuilder sb = new StringBuilder(8192);
|
||||
for (int i = 0; i < 512; i++) {
|
||||
sb.append("0123456789ABCDEF");
|
||||
}
|
||||
TEXT_8K = sb.toString();
|
||||
BYTES_8K = TEXT_8K.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@Test
|
||||
public void testPathParmsRootNone() throws Exception {
|
||||
pathParamTest("/", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooNone() throws Exception {
|
||||
pathParamTest("/foo", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsRootSessionOnly() throws Exception {
|
||||
pathParamTest("/;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionOnly() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummy() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummyValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionBar() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamsRedirect() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Must have a real docBase. Don't use java.io.tmpdir as it may not be
|
||||
// writable.
|
||||
File docBase = new File(getTemporaryDirectory(), "testCoyoteAdapter");
|
||||
addDeleteOnTearDown(docBase);
|
||||
if (!docBase.mkdirs() && !docBase.isDirectory()) {
|
||||
Assert.fail("Failed to create: [" + docBase.toString() + "]");
|
||||
}
|
||||
|
||||
// Create the folder that will trigger the redirect
|
||||
File foo = new File(docBase, "foo");
|
||||
addDeleteOnTearDown(foo);
|
||||
if (!foo.mkdirs() && !foo.isDirectory()) {
|
||||
Assert.fail("Unable to create foo directory in docBase");
|
||||
}
|
||||
|
||||
Context ctx = tomcat.addContext("", docBase.getAbsolutePath());
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
testPath("/", "none");
|
||||
testPath("/;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234;=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
private void pathParamTest(String path, String expected) throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private void testPath(String path, String expected) throws Exception {
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private static class PathParamServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
String sessionId = req.getRequestedSessionId();
|
||||
if (sessionId == null) {
|
||||
sessionId = "none";
|
||||
}
|
||||
pw.write(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtRootNoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1NoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1WithParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah;x=y/blah.txt", "none");
|
||||
}
|
||||
|
||||
private void pathParamExtensionTest(String path, String expected)
|
||||
throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("/testapp", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("*.txt", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602a() throws Exception {
|
||||
// No UTF-8
|
||||
doTestUriDecoding("/foo", "UTF-8", "/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602b() throws Exception {
|
||||
// Valid UTF-8
|
||||
doTestUriDecoding("/foo%c4%87", "UTF-8", "/foo\u0107");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602c() throws Exception {
|
||||
// Partial UTF-8
|
||||
doTestUriDecoding("/foo%c4", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602d() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ff", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602e() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ed%a0%80", "UTF-8", "/foo\uFFFD\uFFFD\uFFFD");
|
||||
}
|
||||
|
||||
private void doTestUriDecoding(String path, String encoding,
|
||||
String expectedPathInfo) throws Exception{
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
tomcat.getConnector().setURIEncoding(encoding);
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
PathInfoServlet servlet = new PathInfoServlet();
|
||||
Tomcat.addServlet(ctx, "servlet", servlet);
|
||||
ctx.addServletMappingDecoded("/*", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + path,
|
||||
new ByteChunk(), null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
|
||||
Assert.assertEquals(expectedPathInfo, servlet.getPathInfo());
|
||||
}
|
||||
|
||||
private static class PathInfoServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private volatile String pathInfo = null;
|
||||
|
||||
public String getPathInfo() {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Not thread safe. Concurrent requests to this servlet will
|
||||
// over-write all the results but the last processed.
|
||||
pathInfo = req.getPathInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug54928() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
AsyncServlet servlet = new AsyncServlet();
|
||||
Wrapper w = Tomcat.addServlet(ctx, "async", servlet);
|
||||
w.setAsyncSupported(true);
|
||||
ctx.addServletMappingDecoded("/async", "async");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
SimpleHttpClient client = new SimpleHttpClient() {
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String request = "GET /async HTTP/1.1" + SimpleHttpClient.CRLF +
|
||||
"Host: a" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF;
|
||||
|
||||
client.setPort(getPort());
|
||||
client.setRequest(new String[] {request});
|
||||
|
||||
client.connect();
|
||||
client.sendRequest();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
String line = client.readLine();
|
||||
if (line != null && line.length() > 20) {
|
||||
log.info(line.subSequence(0, 20) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect();
|
||||
|
||||
// Wait for server thread to stop
|
||||
Thread t = servlet.getThread();
|
||||
long startTime = System.nanoTime();
|
||||
t.join(5000);
|
||||
long endTime = System.nanoTime();
|
||||
log.info("Waited for servlet thread to stop for "
|
||||
+ (endTime - startTime) / 1000000 + " ms");
|
||||
|
||||
Assert.assertTrue(servlet.isCompleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalize01() {
|
||||
doTestNormalize("/foo/../bar", "/bar");
|
||||
}
|
||||
|
||||
private void doTestNormalize(String input, String expected) {
|
||||
MessageBytes mb = MessageBytes.newInstance();
|
||||
byte[] b = input.getBytes(StandardCharsets.UTF_8);
|
||||
mb.setBytes(b, 0, b.length);
|
||||
|
||||
boolean result = CoyoteAdapter.normalize(mb);
|
||||
mb.toString();
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertFalse(result);
|
||||
} else {
|
||||
Assert.assertTrue(result);
|
||||
Assert.assertEquals(expected, mb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AsyncServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// This is a hack that won't work generally as servlets are expected to
|
||||
// handle more than one request.
|
||||
private Thread t;
|
||||
private volatile boolean completed = false;
|
||||
|
||||
public Thread getThread() {
|
||||
return t;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
final OutputStream os = resp.getOutputStream();
|
||||
|
||||
final AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(3000);
|
||||
|
||||
t = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
try {
|
||||
// Some tests depend on this write failing (e.g.
|
||||
// because the client has gone away). In some cases
|
||||
// there may be a large (ish) buffer to fill before
|
||||
// the write fails.
|
||||
for (int j = 0 ; j < 8; j++) {
|
||||
os.write(BYTES_8K);
|
||||
}
|
||||
os.flush();
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("Exception caught " + e);
|
||||
try {
|
||||
// Note if request times out before this
|
||||
// exception is thrown and the complete call
|
||||
// below is made, the complete call below will
|
||||
// fail since the timeout will have completed
|
||||
// the request.
|
||||
asyncCtxt.complete();
|
||||
break;
|
||||
} finally {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
t.setName("testBug54928");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterData;
|
||||
|
||||
/*
|
||||
* Various requests, usually originating from fuzzing, that have triggered an
|
||||
* incorrect response from Tomcat - usually a 500 response rather than a 400
|
||||
* response.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
|
||||
|
||||
private static final String VALUE_16K = TesterData.string('x', 16 * 1024);
|
||||
// Default max header count is 100
|
||||
private static final String HEADER_150 = TesterData.string("X-Tomcat-Test: a" + CRLF, 150);
|
||||
// Default max header count is 200 (need to keep under maxHeaderCount as well)
|
||||
private static final String COOKIE_250 = TesterData.string("Cookie: a=b;c=d;e=f;g=h" + CRLF, 75);
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: requestline[{0}], expected[{2}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
parameterSets.add(new Object[] { "GET /00 HTTP/1.1",
|
||||
"Host: lÿ#" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET *; HTTP/1.1",
|
||||
"Host: localhost" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /02 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
"Content-Length: \u00A0" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /03 HTTP/1.1",
|
||||
"Content-Length: 1" + CRLF +
|
||||
"Content-Length: 1" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /04 HTTP/1.1",
|
||||
"Transfer-Encoding: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /05 HTTP/1.1",
|
||||
"Expect: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /06 HTTP/1.1",
|
||||
"Connection: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /07 HTTP/1.1",
|
||||
"User-Agent: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /08 HTTP/1.1",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET http://host/09 HTTP/1.0",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /10 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
COOKIE_250,
|
||||
"400" } );
|
||||
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public String requestLine;
|
||||
|
||||
@Parameter(1)
|
||||
public String headers;
|
||||
|
||||
@Parameter(2)
|
||||
public String expected;
|
||||
|
||||
|
||||
@Test
|
||||
public void doTest() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
tomcat.getConnector().setAttribute("restrictedUserAgents", "value-not-important");
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName());
|
||||
ctxt.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Client client = new Client(tomcat.getConnector().getLocalPort());
|
||||
client.setRequest(new String[] {requestLine + CRLF, headers + CRLF});
|
||||
|
||||
client.connect();
|
||||
client.processRequest();
|
||||
|
||||
// Expected response
|
||||
String line = client.getResponseLine();
|
||||
Assert.assertTrue(line + CRLF + client.getResponseBody(), line.startsWith("HTTP/1.1 " + expected + " "));
|
||||
}
|
||||
|
||||
|
||||
private static final class Client extends SimpleHttpClient {
|
||||
|
||||
public Client(int port) {
|
||||
setPort(port);
|
||||
setRequestPause(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream createOutputStream(Socket socket) throws IOException {
|
||||
// Override the default implementation so we can create a large
|
||||
// enough buffer to hold the entire request.
|
||||
// The default implementation uses the 8k buffer in the
|
||||
// StreamEncoder. Since some requests are larger than this, those
|
||||
// requests will be sent in several parts. If the first part is
|
||||
// sufficient for Tomcat to determine the request is invalid, Tomcat
|
||||
// will close the connection, causing the write of the remaining
|
||||
// parts to fail which in turn causes the test to fail.
|
||||
return new BufferedOutputStream(super.createOutputStream(socket), 32 * 1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
// Response body varies. It is the response code that is of interest
|
||||
// in these tests.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteInputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testReadWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
String requestBody = "HelloWorld";
|
||||
int rc = postUrl(requestBody.getBytes(StandardCharsets.UTF_8),
|
||||
"http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertTrue(requestBody.equals(bc.toString()));
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteInputStream is = (CoyoteInputStream) req.getInputStream();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(256);
|
||||
is.read(buffer);
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
os.write(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteOutputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
ByteChunk expected = new ByteChunk();
|
||||
expected.append(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
Assert.assertTrue(expected.equals(bc));
|
||||
}
|
||||
}
|
||||
|
||||
private void doNonBlockingTest(int asyncWriteTarget, int syncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w = Tomcat.addServlet(root, "nbWrite",
|
||||
new NonBlockingWriteServlet(asyncWriteTarget, useContainerThreadToSetListener));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/nbWrite", "nbWrite");
|
||||
Tomcat.addServlet(root, "write",
|
||||
new BlockingWriteServlet(asyncWriteTarget, syncWriteTarget));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/write", "write");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
// Extend timeout to 5 mins for debugging
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/nbWrite", bc,
|
||||
300000, null, null);
|
||||
|
||||
int totalCount = asyncWriteTarget + syncWriteTarget;
|
||||
StringBuilder sb = new StringBuilder(totalCount * 16);
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
sb.append("OK - " + i + System.lineSeparator());
|
||||
}
|
||||
String expected = null;
|
||||
if (sb.length() > 0) {
|
||||
expected = sb.toString();
|
||||
}
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals(expected, bc.toString());
|
||||
}
|
||||
|
||||
private static final class NonBlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int asyncWriteTarget;
|
||||
private final AtomicInteger asyncWriteCount = new AtomicInteger(0);
|
||||
private final boolean useContainerThreadToSetListener;
|
||||
|
||||
public NonBlockingWriteServlet(int asyncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) {
|
||||
this.asyncWriteTarget = asyncWriteTarget;
|
||||
this.useContainerThreadToSetListener = useContainerThreadToSetListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
|
||||
AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(5);
|
||||
Runnable task = new AsyncTask(asyncCtxt, sos);
|
||||
if (useContainerThreadToSetListener) {
|
||||
asyncCtxt.start(task);
|
||||
} else {
|
||||
Thread t = new Thread(task);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void doAsyncWrite(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) throws IOException {
|
||||
while (sos.isReady()) {
|
||||
int next = asyncWriteCount.getAndIncrement();
|
||||
if (next < asyncWriteTarget) {
|
||||
sos.write(
|
||||
("OK - " + next + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
sos.flush();
|
||||
} else {
|
||||
asyncCtxt.dispatch("/write");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncTask implements Runnable {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public AsyncTask(AsyncContext asyncCtxt, ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sos.setWriteListener(new MyWriteListener(asyncCtxt, sos));
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyWriteListener implements WriteListener {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public MyWriteListener(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWritePossible() throws IOException {
|
||||
doAsyncWrite(asyncCtxt, sos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
// Not expected.
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int start;
|
||||
private final int len;
|
||||
|
||||
public BlockingWriteServlet(int start, int len) {
|
||||
this.start = start;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
for (int i = start; i < start + len; i++) {
|
||||
sos.write(("OK - " + i + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
os.write(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.TestUtf8;
|
||||
import org.apache.tomcat.util.buf.TestUtf8.Utf8TestCase;
|
||||
|
||||
public class TestInputBuffer extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testUtf8Body() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Echo", new Utf8Echo());
|
||||
root.addServletMappingDecoded("/test", "Echo");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
for (Utf8TestCase testCase : TestUtf8.TEST_CASES) {
|
||||
String expected = null;
|
||||
if (testCase.invalidIndex == -1) {
|
||||
expected = testCase.outputReplaced;
|
||||
}
|
||||
doUtf8BodyTest(testCase.description, testCase.input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60400() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Bug60400Servlet", new Bug60400Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug60400Servlet");
|
||||
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("socket.appReadBufSize", "9000"));
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
byte[] requestBody = new byte[9500];
|
||||
Arrays.fill(requestBody, (byte) 1);
|
||||
int rc = postUrl(requestBody, "http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(requestBody.length, bc.getLength());
|
||||
}
|
||||
|
||||
|
||||
private void doUtf8BodyTest(String description, int[] input,
|
||||
String expected) throws Exception {
|
||||
|
||||
byte[] bytes = new byte[input.length];
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
bytes[i] = (byte) input[i];
|
||||
}
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = postUrl(bytes, "http://localhost:" + getPort() + "/test", bc,
|
||||
null);
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertEquals(description, HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(description, "FAILED", bc.toString());
|
||||
} else if (expected.length() == 0) {
|
||||
Assert.assertNull(description, bc.toString());
|
||||
} else {
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(description, expected, bc.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8Echo extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Should use POST
|
||||
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
Reader r = req.getReader();
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
try {
|
||||
// Copy one character at a time
|
||||
int c = r.read();
|
||||
while (c != -1) {
|
||||
w.write(c);
|
||||
c = r.read();
|
||||
}
|
||||
w.close();
|
||||
} catch (MalformedInputException mie) {
|
||||
resp.resetBuffer();
|
||||
w.write("FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Bug60400Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (BufferedReader reader = req.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
}
|
||||
resp.getWriter().print(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestKeepAliveCount extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testHttp10() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp10Request();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttp11() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp11Request();
|
||||
}
|
||||
|
||||
|
||||
private class TestKeepAliveClient extends SimpleHttpClient {
|
||||
|
||||
|
||||
private boolean init;
|
||||
|
||||
private synchronized void init() {
|
||||
if (init) return;
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "5"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
init = true;
|
||||
}
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
processRequest(false); // blocks until response has been read
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
private void doHttp11Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.1" + CRLF +
|
||||
"Host: localhost" + CRLF +
|
||||
"Connection: Keep-Alive" + CRLF+
|
||||
"Keep-Alive: 300"+ CRLF+ CRLF;
|
||||
|
||||
setRequest(request);
|
||||
|
||||
for (int i=0; i<5; i++) {
|
||||
processRequest(false); // blocks until response has been read
|
||||
Assert.assertTrue(getResponseLine()!=null && getResponseLine().startsWith("HTTP/1.1 200 "));
|
||||
}
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestMaxConnections extends TomcatBaseTest {
|
||||
private static final int MAX_CONNECTIONS = 3;
|
||||
public static final int soTimeout = 5000;
|
||||
public static final int connectTimeout = 1000;
|
||||
|
||||
@Test
|
||||
public void testConnector() throws Exception {
|
||||
init();
|
||||
Assume.assumeFalse("This feature is not available for NIO2 (BZ58103)",
|
||||
getTomcatInstance().getConnector().getProtocolHandlerClassName().contains("Nio2"));
|
||||
ConnectThread[] t = new ConnectThread[10];
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i] = new ConnectThread();
|
||||
t[i].setName("ConnectThread["+i+"]");
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].start();
|
||||
Thread.sleep(50);
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].join();
|
||||
}
|
||||
|
||||
Assert.assertEquals(MAX_CONNECTIONS, SimpleServlet.getMaxConnections());
|
||||
}
|
||||
|
||||
private class ConnectThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
TestClient client = new TestClient();
|
||||
client.doHttp10Request();
|
||||
} catch (Exception x) {
|
||||
// NO-OP. Some connections are expected to fail.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private synchronized void init() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
StandardContext root = (StandardContext) tomcat.addContext("", SimpleHttpClient.TEMP_DIR);
|
||||
root.setUnloadDelay(soTimeout);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxThreads", "10"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxConnections", Integer.toString(MAX_CONNECTIONS)));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("acceptCount", "1"));
|
||||
tomcat.start();
|
||||
}
|
||||
|
||||
private class TestClient extends SimpleHttpClient {
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
setPort(getPort());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Open connection
|
||||
connect(connectTimeout,soTimeout);
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
boolean passed = false;
|
||||
processRequest(false); // blocks until response has been read
|
||||
long stop = System.currentTimeMillis();
|
||||
log.info(Thread.currentThread().getName()+" Request complete:"+(stop-start)+" ms.");
|
||||
passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static int currentConnections = 0;
|
||||
private static int maxConnections = 0;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
increment();
|
||||
|
||||
System.out.println("Processing thread: " + Thread.currentThread().getName());
|
||||
try {
|
||||
Thread.sleep(TestMaxConnections.soTimeout*4/5);
|
||||
} catch (InterruptedException x) {
|
||||
|
||||
}
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
|
||||
decrement();
|
||||
}
|
||||
|
||||
private static synchronized void increment() {
|
||||
currentConnections++;
|
||||
if (currentConnections > maxConnections) {
|
||||
maxConnections = currentConnections;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void decrement() {
|
||||
currentConnections--;
|
||||
}
|
||||
|
||||
|
||||
public static synchronized int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestOutputBuffer extends TomcatBaseTest{
|
||||
|
||||
/*
|
||||
* Expect that the buffered results are slightly slower since Tomcat now has
|
||||
* an internal buffer so an extra one just adds overhead.
|
||||
*
|
||||
* @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=52328"
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
WritingServlet servlet = new WritingServlet(i);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
int rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i, bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
|
||||
rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i + "?useBuffer=y", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug52577() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
Bug52577Servlet bug52577 = new Bug52577Servlet();
|
||||
Tomcat.addServlet(root, "bug52577", bug52577);
|
||||
root.addServletMappingDecoded("/", "bug52577");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
}
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
private final String writeString;
|
||||
private final int writeCount;
|
||||
|
||||
public WritingServlet(int writeLength) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < writeLength; i++) {
|
||||
sb.append('x');
|
||||
}
|
||||
writeString = sb.toString();
|
||||
writeCount = EXPECTED_CONTENT_LENGTH / writeLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
// Wrap with a buffer if necessary
|
||||
String useBufferStr = req.getParameter("useBuffer");
|
||||
if (useBufferStr != null) {
|
||||
w = new BufferedWriter(w);
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
for (int i = 0; i < writeCount; i++) {
|
||||
w.write(writeString);
|
||||
}
|
||||
if (useBufferStr != null) {
|
||||
w.flush();
|
||||
}
|
||||
long lastRunNano = System.nanoTime() - start;
|
||||
|
||||
System.out.println("Write length: " + writeString.length() +
|
||||
", Buffered: " + (useBufferStr == null ? "n" : "y") +
|
||||
", Time: " + lastRunNano + "ns");
|
||||
}
|
||||
}
|
||||
|
||||
private static class Bug52577Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
Writer w = resp.getWriter();
|
||||
w.write("OK");
|
||||
resp.resetBuffer();
|
||||
w.write("OK");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUtf8SurrogateBody() throws Exception {
|
||||
// Create test data. This is carefully constructed to trigger the edge
|
||||
// case. Small variations may cause the test to miss the edge case.
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("a");
|
||||
|
||||
for (int i = 0x10000; i < 0x11000; i++) {
|
||||
char[] chars = Character.toChars(i);
|
||||
sb.append(chars);
|
||||
}
|
||||
String data = sb.toString();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Test", new Utf8WriteChars(data));
|
||||
root.addServletMappingDecoded("/test", "Test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test", bc, null);
|
||||
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(data, bc.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8WriteChars extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final char[] chars;
|
||||
|
||||
public Utf8WriteChars(String data) {
|
||||
chars = data.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
w.write(chars[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
@@ -0,0 +1,969 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.authenticator.BasicAuthenticator;
|
||||
import org.apache.catalina.filters.FailedRequestFilter;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.TesterMapRealm;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterRequest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.FilterDef;
|
||||
import org.apache.tomcat.util.descriptor.web.FilterMap;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
|
||||
/**
|
||||
* Test case for {@link Request}.
|
||||
*/
|
||||
public class TestRequest extends TomcatBaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
// Some of these tests need this and it used statically so set it once
|
||||
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for https://bz.apache.org/bugzilla/show_bug.cgi?id=37794
|
||||
* POST parameters are not returned from a call to
|
||||
* any of the {@link HttpServletRequest} getParameterXXX() methods if the
|
||||
* request is chunked.
|
||||
*/
|
||||
@Test
|
||||
public void testBug37794() {
|
||||
Bug37794Client client = new Bug37794Client(true);
|
||||
|
||||
// Edge cases around zero
|
||||
client.doRequest(-1, false); // Unlimited
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertTrue(client.isResponseBodyOK());
|
||||
client.reset();
|
||||
client.doRequest(0, false); // 0 bytes - too small should fail
|
||||
Assert.assertTrue(client.isResponse413());
|
||||
client.reset();
|
||||
client.doRequest(1, false); // 1 byte - too small should fail
|
||||
Assert.assertTrue(client.isResponse413());
|
||||
|
||||
client.reset();
|
||||
|
||||
// Edge cases around actual content length
|
||||
client.reset();
|
||||
client.doRequest(6, false); // Too small should fail
|
||||
Assert.assertTrue(client.isResponse413());
|
||||
client.reset();
|
||||
client.doRequest(7, false); // Just enough should pass
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertTrue(client.isResponseBodyOK());
|
||||
client.reset();
|
||||
client.doRequest(8, false); // 1 extra - should pass
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertTrue(client.isResponseBodyOK());
|
||||
|
||||
// Much larger
|
||||
client.reset();
|
||||
client.doRequest(8096, false); // Plenty of space - should pass
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertTrue(client.isResponseBodyOK());
|
||||
|
||||
// Check for case insensitivity
|
||||
client.reset();
|
||||
client.doRequest(8096, true); // Plenty of space - should pass
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertTrue(client.isResponseBodyOK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional test for failed requests handling when no FailedRequestFilter
|
||||
* is defined.
|
||||
*/
|
||||
@Test
|
||||
public void testBug37794withoutFilter() {
|
||||
Bug37794Client client = new Bug37794Client(false);
|
||||
|
||||
// Edge cases around actual content length
|
||||
client.reset();
|
||||
client.doRequest(6, false); // Too small should fail
|
||||
// Response code will be OK, but parameters list will be empty
|
||||
Assert.assertTrue(client.isResponse200());
|
||||
Assert.assertEquals("", client.getResponseBody());
|
||||
}
|
||||
|
||||
private static class Bug37794Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Only interested in the parameters and values for POST requests.
|
||||
*/
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Just echo the parameters and values back as plain text
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
|
||||
// Assume one value per attribute
|
||||
Enumeration<String> names = req.getParameterNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
out.println(name + "=" + req.getParameter(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug 37794 test client.
|
||||
*/
|
||||
private class Bug37794Client extends SimpleHttpClient {
|
||||
|
||||
private final boolean createFilter;
|
||||
|
||||
private boolean init;
|
||||
|
||||
public Bug37794Client(boolean createFilter) {
|
||||
this.createFilter = createFilter;
|
||||
}
|
||||
|
||||
private synchronized void init() throws Exception {
|
||||
if (init) return;
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Bug37794", new Bug37794Servlet());
|
||||
root.addServletMappingDecoded("/test", "Bug37794");
|
||||
|
||||
if (createFilter) {
|
||||
FilterDef failedRequestFilter = new FilterDef();
|
||||
failedRequestFilter.setFilterName("failedRequestFilter");
|
||||
failedRequestFilter.setFilterClass(
|
||||
FailedRequestFilter.class.getName());
|
||||
FilterMap failedRequestFilterMap = new FilterMap();
|
||||
failedRequestFilterMap.setFilterName("failedRequestFilter");
|
||||
failedRequestFilterMap.addURLPatternDecoded("/*");
|
||||
root.addFilterDef(failedRequestFilter);
|
||||
root.addFilterMap(failedRequestFilterMap);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
private Exception doRequest(int postLimit, boolean ucChunkedHead) {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
try {
|
||||
init();
|
||||
tomcat.getConnector().setMaxPostSize(postLimit);
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[2];
|
||||
if (ucChunkedHead) {
|
||||
request[0] =
|
||||
"POST http://localhost:8080/test HTTP/1.1" + CRLF +
|
||||
"Host: localhost:8080" + CRLF +
|
||||
"content-type: application/x-www-form-urlencoded" + CRLF +
|
||||
"Transfer-Encoding: CHUNKED" + CRLF +
|
||||
"Connection: close" + CRLF +
|
||||
CRLF +
|
||||
"3" + CRLF +
|
||||
"a=1" + CRLF;
|
||||
} else {
|
||||
request[0] =
|
||||
"POST http://localhost:8080/test HTTP/1.1" + CRLF +
|
||||
"Host: localhost:8080" + CRLF +
|
||||
"content-type: application/x-www-form-urlencoded" + CRLF +
|
||||
"Transfer-Encoding: chunked" + CRLF +
|
||||
"Connection: close" + CRLF +
|
||||
CRLF +
|
||||
"3" + CRLF +
|
||||
"a=1" + CRLF;
|
||||
}
|
||||
request[1] =
|
||||
"4" + CRLF +
|
||||
"&b=2" + CRLF +
|
||||
"0" + CRLF +
|
||||
CRLF;
|
||||
|
||||
setRequest(request);
|
||||
processRequest(); // blocks until response has been read
|
||||
|
||||
// Close the connection
|
||||
disconnect();
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
if (getResponseBody() == null) {
|
||||
return false;
|
||||
}
|
||||
if (!getResponseBody().contains("a=1")) {
|
||||
return false;
|
||||
}
|
||||
if (!getResponseBody().contains("b=2")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Test case for
|
||||
* <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=38113">bug
|
||||
* 38118</a>.
|
||||
*/
|
||||
@Test
|
||||
public void testBug38113() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Add the Servlet
|
||||
Tomcat.addServlet(ctx, "servlet", new EchoQueryStringServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
// No query string
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + "/");
|
||||
Assert.assertEquals("QueryString=null", res.toString());
|
||||
|
||||
// Query string
|
||||
res = getUrl("http://localhost:" + getPort() + "/?a=b");
|
||||
Assert.assertEquals("QueryString=a=b", res.toString());
|
||||
|
||||
// Empty string
|
||||
res = getUrl("http://localhost:" + getPort() + "/?");
|
||||
Assert.assertEquals("QueryString=", res.toString());
|
||||
}
|
||||
|
||||
private static final class EchoQueryStringServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.print("QueryString=" + req.getQueryString());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test case for {@link Request#login(String, String)} and
|
||||
* {@link Request#logout()}.
|
||||
*/
|
||||
@Test
|
||||
public void testLoginLogout() throws Exception{
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
LoginConfig config = new LoginConfig();
|
||||
config.setAuthMethod("BASIC");
|
||||
ctx.setLoginConfig(config);
|
||||
ctx.getPipeline().addValve(new BasicAuthenticator());
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new LoginLogoutServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
TesterMapRealm realm = new TesterMapRealm();
|
||||
realm.addUser(LoginLogoutServlet.USER, LoginLogoutServlet.PWD);
|
||||
ctx.setRealm(realm);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + "/");
|
||||
Assert.assertEquals(LoginLogoutServlet.OK, res.toString());
|
||||
}
|
||||
|
||||
private static final class LoginLogoutServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String USER = "user";
|
||||
private static final String PWD = "pwd";
|
||||
private static final String OK = "OK";
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
req.login(USER, PWD);
|
||||
|
||||
if (!req.getRemoteUser().equals(USER))
|
||||
throw new ServletException();
|
||||
if (!req.getUserPrincipal().getName().equals(USER))
|
||||
throw new ServletException();
|
||||
|
||||
req.logout();
|
||||
|
||||
if (req.getRemoteUser() != null)
|
||||
throw new ServletException();
|
||||
if (req.getUserPrincipal() != null)
|
||||
throw new ServletException();
|
||||
|
||||
resp.getWriter().write(OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug49424NoChunking() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("",
|
||||
System.getProperty("java.io.tmpdir"));
|
||||
Tomcat.addServlet(root, "Bug37794", new Bug37794Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug37794");
|
||||
tomcat.start();
|
||||
|
||||
HttpURLConnection conn = getConnection("http://localhost:" + getPort() + "/");
|
||||
InputStream is = conn.getInputStream();
|
||||
Assert.assertNotNull(is);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug49424WithChunking() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("",
|
||||
System.getProperty("java.io.tmpdir"));
|
||||
Tomcat.addServlet(root, "Bug37794", new Bug37794Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug37794");
|
||||
tomcat.start();
|
||||
|
||||
HttpURLConnection conn = getConnection("http://localhost:" + getPort() + "/");
|
||||
conn.setChunkedStreamingMode(8 * 1024);
|
||||
InputStream is = conn.getInputStream();
|
||||
Assert.assertNotNull(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for https://bz.apache.org/bugzilla/show_bug.cgi?id=48692
|
||||
* PUT requests should be able to fetch request parameters coming from
|
||||
* the request body (when properly configured using the new parseBodyMethod
|
||||
* setting).
|
||||
*/
|
||||
@Test
|
||||
public void testBug48692() {
|
||||
Bug48692Client client = new Bug48692Client();
|
||||
|
||||
// Make sure GET works properly
|
||||
client.doRequest("GET", "foo=bar", null, null, false);
|
||||
|
||||
Assert.assertTrue("Non-200 response for GET request",
|
||||
client.isResponse200());
|
||||
Assert.assertEquals("Incorrect response for GET request",
|
||||
"foo=bar",
|
||||
client.getResponseBody());
|
||||
|
||||
client.reset();
|
||||
|
||||
//
|
||||
// Make sure POST works properly
|
||||
//
|
||||
// POST with separate GET and POST parameters
|
||||
client.doRequest("POST", "foo=bar", "application/x-www-form-urlencoded", "bar=baz", true);
|
||||
|
||||
Assert.assertTrue("Non-200 response for POST request",
|
||||
client.isResponse200());
|
||||
Assert.assertEquals("Incorrect response for POST request",
|
||||
"bar=baz,foo=bar",
|
||||
client.getResponseBody());
|
||||
|
||||
client.reset();
|
||||
|
||||
// POST with overlapping GET and POST parameters
|
||||
client.doRequest("POST", "foo=bar&bar=foo", "application/x-www-form-urlencoded", "bar=baz&foo=baz", true);
|
||||
|
||||
Assert.assertTrue("Non-200 response for POST request",
|
||||
client.isResponse200());
|
||||
Assert.assertEquals("Incorrect response for POST request",
|
||||
"bar=baz,bar=foo,foo=bar,foo=baz",
|
||||
client.getResponseBody());
|
||||
|
||||
client.reset();
|
||||
|
||||
// PUT without POST-style parsing
|
||||
client.doRequest("PUT", "foo=bar&bar=foo", "application/x-www-form-urlencoded", "bar=baz&foo=baz", false);
|
||||
|
||||
Assert.assertTrue("Non-200 response for PUT/noparse request",
|
||||
client.isResponse200());
|
||||
Assert.assertEquals("Incorrect response for PUT request",
|
||||
"bar=foo,foo=bar",
|
||||
client.getResponseBody());
|
||||
|
||||
client.reset();
|
||||
|
||||
// PUT with POST-style parsing
|
||||
client.doRequest("PUT", "foo=bar&bar=foo", "application/x-www-form-urlencoded", "bar=baz&foo=baz", true);
|
||||
|
||||
Assert.assertTrue("Non-200 response for PUT request",
|
||||
client.isResponse200());
|
||||
Assert.assertEquals("Incorrect response for PUT/parse request",
|
||||
"bar=baz,bar=foo,foo=bar,foo=baz",
|
||||
client.getResponseBody());
|
||||
|
||||
client.reset();
|
||||
|
||||
/*
|
||||
private Exception doRequest(String method,
|
||||
String queryString,
|
||||
String contentType,
|
||||
String requestBody,
|
||||
boolean allowBody) {
|
||||
*/
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54984() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("",
|
||||
System.getProperty("java.io.tmpdir"));
|
||||
root.setAllowCasualMultipartParsing(true);
|
||||
Tomcat.addServlet(root, "Bug54984", new Bug54984Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug54984");
|
||||
tomcat.start();
|
||||
|
||||
HttpURLConnection conn = getConnection("http://localhost:" + getPort()
|
||||
+ "/parseParametersBeforeParseParts");
|
||||
|
||||
prepareRequestBug54984(conn);
|
||||
|
||||
checkResponseBug54984(conn);
|
||||
|
||||
conn.disconnect();
|
||||
|
||||
conn = getConnection("http://localhost:" + getPort() + "/");
|
||||
|
||||
prepareRequestBug54984(conn);
|
||||
|
||||
checkResponseBug54984(conn);
|
||||
|
||||
conn.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static class EchoParametersServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Only interested in the parameters and values for requests.
|
||||
* Note: echos parameters in alphabetical order.
|
||||
*/
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Just echo the parameters and values back as plain text
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
|
||||
TreeMap<String,String[]> parameters =
|
||||
new TreeMap<>(req.getParameterMap());
|
||||
|
||||
boolean first = true;
|
||||
|
||||
for(String name: parameters.keySet()) {
|
||||
String[] values = req.getParameterValues(name);
|
||||
|
||||
java.util.Arrays.sort(values);
|
||||
|
||||
for(int i=0; i<values.length; ++i)
|
||||
{
|
||||
if(first)
|
||||
first = false;
|
||||
else
|
||||
out.print(",");
|
||||
|
||||
out.print(name + "=" + values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug 48692 test client: test for allowing PUT request bodies.
|
||||
*/
|
||||
private class Bug48692Client extends SimpleHttpClient {
|
||||
|
||||
private boolean init;
|
||||
|
||||
private synchronized void init() throws Exception {
|
||||
if (init) return;
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "EchoParameters", new EchoParametersServlet());
|
||||
root.addServletMappingDecoded("/echo", "EchoParameters");
|
||||
tomcat.start();
|
||||
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
private Exception doRequest(String method,
|
||||
String queryString,
|
||||
String contentType,
|
||||
String requestBody,
|
||||
boolean allowBody) {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
try {
|
||||
init();
|
||||
if(allowBody)
|
||||
tomcat.getConnector().setParseBodyMethods(method);
|
||||
else
|
||||
tomcat.getConnector().setParseBodyMethods(""); // never parse
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Re-encode the request body so that bytes = characters
|
||||
if(null != requestBody)
|
||||
requestBody = new String(requestBody.getBytes("UTF-8"), "ASCII");
|
||||
|
||||
// Send specified request body using method
|
||||
String[] request = {
|
||||
(
|
||||
method + " http://localhost:" + getPort() + "/echo"
|
||||
+ (null == queryString ? "" : ("?" + queryString))
|
||||
+ " HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:" + getPort() + CRLF
|
||||
+ (null == contentType ? ""
|
||||
: ("Content-Type: " + contentType + CRLF))
|
||||
+ "Connection: close" + CRLF
|
||||
+ (null == requestBody ? "" : "Content-Length: " + requestBody.length() + CRLF)
|
||||
+ CRLF
|
||||
+ (null == requestBody ? "" : requestBody)
|
||||
)
|
||||
};
|
||||
|
||||
setRequest(request);
|
||||
processRequest(); // blocks until response has been read
|
||||
|
||||
// Close the connection
|
||||
disconnect();
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return false; // Don't care
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String query) throws IOException {
|
||||
URL postURL;
|
||||
postURL = new URL(query);
|
||||
HttpURLConnection conn = (HttpURLConnection) postURL.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
conn.setAllowUserInteraction(false);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
private static class Bug54984Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
|
||||
if (req.getRequestURI().endsWith("parseParametersBeforeParseParts")) {
|
||||
req.getParameterNames();
|
||||
}
|
||||
|
||||
req.getPart("part");
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
resp.getWriter().println("Part " + req.getParameter("part"));
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareRequestBug54984(HttpURLConnection conn)
|
||||
throws Exception {
|
||||
String boundary = "-----" + System.currentTimeMillis();
|
||||
conn.setRequestProperty("Content-Type",
|
||||
"multipart/form-data; boundary=" + boundary);
|
||||
|
||||
try (OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
|
||||
PrintWriter writer = new PrintWriter(osw, true)) {
|
||||
writer.append("--" + boundary).append("\r\n");
|
||||
writer.append("Content-Disposition: form-data; name=\"part\"\r\n");
|
||||
writer.append("Content-Type: text/plain; charset=UTF-8\r\n");
|
||||
writer.append("\r\n");
|
||||
writer.append("äö").append("\r\n");
|
||||
writer.flush();
|
||||
|
||||
writer.append("\r\n");
|
||||
writer.flush();
|
||||
|
||||
writer.append("--" + boundary + "--").append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkResponseBug54984(HttpURLConnection conn)
|
||||
throws Exception {
|
||||
List<String> response = new ArrayList<>();
|
||||
int status = conn.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
try (InputStreamReader isr = new InputStreamReader(conn.getInputStream(), "UTF-8");
|
||||
BufferedReader reader = new BufferedReader(isr)) {
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.add(line);
|
||||
}
|
||||
Assert.assertTrue(response.contains("Part äö"));
|
||||
}
|
||||
} else {
|
||||
Assert.fail("OK status was expected: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501a() throws Exception {
|
||||
doBug56501("/path", "/path", "/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501b() throws Exception {
|
||||
doBug56501("/path", "/path/", "/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501c() throws Exception {
|
||||
doBug56501("/path", "/path/xxx", "/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501d() throws Exception {
|
||||
doBug56501("", "", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501e() throws Exception {
|
||||
doBug56501("", "/", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501f() throws Exception {
|
||||
doBug56501("", "/xxx", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501g() throws Exception {
|
||||
doBug56501("/path/abc", "/path/abc", "/path/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501h() throws Exception {
|
||||
doBug56501("/path/abc", "/path/abc/", "/path/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501i() throws Exception {
|
||||
doBug56501("/path/abc", "/path/abc/xxx", "/path/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501j() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa%5Fth/abc", "/pa%5Fth/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501k() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa%5Fth/abc/", "/pa%5Fth/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501l() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa%5Fth/abc/xxx", "/pa%5Fth/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501m() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa_th/abc", "/pa_th/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501n() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa_th/abc/", "/pa_th/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501o() throws Exception {
|
||||
doBug56501("/pa_th/abc", "/pa_th/abc/xxx", "/pa_th/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501p() throws Exception {
|
||||
doBug56501("/path/abc", "/path;a=b/abc/xxx", "/path;a=b/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501q() throws Exception {
|
||||
doBug56501("/path/abc", "/path/abc;a=b/xxx", "/path/abc;a=b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501r() throws Exception {
|
||||
doBug56501("/path/abc", "/path/abc/xxx;a=b", "/path/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug56501s() throws Exception {
|
||||
doBug56501("/path/abc", "/.;a=b/path/abc/xxx", "/.;a=b/path/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215a() throws Exception {
|
||||
doBug56501("/path", "//path", "//path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215b() throws Exception {
|
||||
doBug56501("/path", "//path/", "//path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215c() throws Exception {
|
||||
doBug56501("/path", "/%2Fpath", "/%2Fpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215d() throws Exception {
|
||||
doBug56501("/path", "/%2Fpath%2F", "/%2Fpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215e() throws Exception {
|
||||
doBug56501("/path", "/foo/../path", "/foo/../path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug57215f() throws Exception {
|
||||
doBug56501("/path", "/foo/..%2fpath", "/foo/..%2fpath");
|
||||
}
|
||||
|
||||
private void doBug56501(String deployPath, String requestPath, String expected)
|
||||
throws Exception {
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(deployPath, null);
|
||||
ctx.setAllowMultipleLeadingForwardSlashInPath(true);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new Bug56501Servlet());
|
||||
ctx.addServletMappingDecoded("/*", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + requestPath);
|
||||
String resultPath = res.toString();
|
||||
if (resultPath == null) {
|
||||
resultPath = "";
|
||||
}
|
||||
Assert.assertEquals(expected, resultPath);
|
||||
}
|
||||
|
||||
private class Bug56501Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().print(req.getContextPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLocaleMultipleHeaders01() throws Exception {
|
||||
TesterRequest req = new TesterRequest();
|
||||
|
||||
req.addHeader("accept-language", "en;q=0.5");
|
||||
req.addHeader("accept-language", "en-gb");
|
||||
|
||||
Locale actual = req.getLocale();
|
||||
Locale expected = Locale.forLanguageTag("en-gb");
|
||||
|
||||
Assert.assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reverse header order of getLocaleMultipleHeaders01() and make sure the
|
||||
* result is the same.
|
||||
*/
|
||||
@Test
|
||||
public void getLocaleMultipleHeaders02() throws Exception {
|
||||
TesterRequest req = new TesterRequest();
|
||||
|
||||
req.addHeader("accept-language", "en-gb");
|
||||
req.addHeader("accept-language", "en;q=0.5");
|
||||
|
||||
Locale actual = req.getLocale();
|
||||
Locale expected = Locale.forLanguageTag("en-gb");
|
||||
|
||||
Assert.assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore("Used to check performance of different parsing approaches")
|
||||
public void localeParsePerformance() throws Exception {
|
||||
TesterRequest req = new TesterRequest();
|
||||
req.addHeader("accept-encoding", "en-gb,en");
|
||||
|
||||
long start = System.nanoTime();
|
||||
|
||||
// Takes about 0.3s on a quad core 2.7Ghz 2013 MacBook
|
||||
for (int i = 0; i < 10000000; i++) {
|
||||
req.parseLocales();
|
||||
req.localesParsed = false;
|
||||
req.locales.clear();
|
||||
}
|
||||
|
||||
long time = System.nanoTime() - start;
|
||||
|
||||
System.out.println(time);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetReaderValidEncoding() throws Exception {
|
||||
doTestGetReader("ISO-8859-1", true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetReaderInvalidEbcoding() throws Exception {
|
||||
doTestGetReader("X-Invalid", false);
|
||||
}
|
||||
|
||||
|
||||
private void doTestGetReader(String userAgentCharaceterEncoding, boolean expect200)
|
||||
throws Exception {
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new Bug61264GetReaderServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
byte[] body = "Test".getBytes();
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String,List<String>> reqHeaders = new HashMap<>();
|
||||
reqHeaders.put("Content-Type",
|
||||
Arrays.asList(new String[] {"text/plain;charset=" + userAgentCharaceterEncoding}));
|
||||
|
||||
int rc = postUrl(body, "http://localhost:" + getPort() + "/", bc, reqHeaders, null);
|
||||
|
||||
if (expect200) {
|
||||
Assert.assertEquals(200, rc);
|
||||
} else {
|
||||
Assert.assertEquals(500, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class Bug61264GetReaderServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// This is intended for POST requests
|
||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Container will handle any errors
|
||||
req.getReader();
|
||||
}
|
||||
}
|
||||
}
|
||||
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterContext;
|
||||
import org.apache.tomcat.unittest.TesterRequest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
|
||||
|
||||
/**
|
||||
* Test case for {@link Request}.
|
||||
*/
|
||||
public class TestResponse extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testBug49598() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new Bug49598Servlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Map<String,List<String>> headers = new CaseInsensitiveKeyMap<>();
|
||||
getUrl("http://localhost:" + getPort() + "/", new ByteChunk(), headers);
|
||||
|
||||
// Check for headers without a name
|
||||
for (Map.Entry<String,List<String>> header : headers.entrySet()) {
|
||||
if (header.getKey() == null) {
|
||||
// Expected if this is the response line
|
||||
List<String> values = header.getValue();
|
||||
if (values.size() == 1 &&
|
||||
values.get(0).startsWith("HTTP/1.1")) {
|
||||
continue;
|
||||
}
|
||||
Assert.fail("Null header name detected for value " + values);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for exactly one Set-Cookie header
|
||||
int count = 0;
|
||||
for (String headerName : headers.keySet()) {
|
||||
if ("Set-Cookie".equals(headerName)) {
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(1, count);
|
||||
}
|
||||
|
||||
private static final class Bug49598Servlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
HttpSession session = req.getSession(true);
|
||||
session.invalidate();
|
||||
req.getSession(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Tests an issue noticed during the investigation of BZ 52811.
|
||||
*/
|
||||
@Test
|
||||
public void testCharset() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new CharsetServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + "/");
|
||||
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
}
|
||||
|
||||
private static final class CharsetServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
PrintWriter pw = resp.getWriter();
|
||||
resp.setHeader("Content-Type", "text/plain;charset=UTF-8");
|
||||
|
||||
// Should be ISO-8859-1 because getWriter() was called before
|
||||
// setHeader()
|
||||
if (resp.getCharacterEncoding().equals("ISO-8859-1")) {
|
||||
pw.print("OK");
|
||||
} else {
|
||||
pw.print("FAIL: " + resp.getCharacterEncoding());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug52811() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new Bug52811Servlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + "/");
|
||||
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062a() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./bar.html");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/level2/bar.html",
|
||||
result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062b() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute(".");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/level2/", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062c() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("..");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062d() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute(".././..");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/", result);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testBug53062e() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
resp.toAbsolute("../../..");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062f() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("bar.html");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/bar.html", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062g() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("bar.html?x=/../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/bar.html?x=/../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062h() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("bar.html?x=/../../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/bar.html?x=/../../",
|
||||
result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062i() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./.?x=/../../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/?x=/../../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062j() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./..?x=/../../");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/?x=/../../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062k() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./..?x=/../..");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/?x=/../..",
|
||||
result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062l() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("bar.html#/../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/bar.html#/../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062m() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("bar.html#/../../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/bar.html#/../../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062n() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./.#/../../");
|
||||
|
||||
Assert.assertEquals(
|
||||
"http://localhost:8080/level1/level2/#/../../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062o() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./..#/../../");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/#/../../", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53062p() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.toAbsolute("./..#/../..");
|
||||
|
||||
Assert.assertEquals("http://localhost:8080/level1/#/../..", result);
|
||||
}
|
||||
|
||||
|
||||
private void doTestEncodeURL(String location, String expected) {
|
||||
Request req = new TesterRequest(true);
|
||||
req.setRequestedSessionId("1234");
|
||||
req.setRequestedSessionURL(true);
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.encodeURL(location);
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL01() throws Exception {
|
||||
doTestEncodeURL("./bar.html", "./bar.html;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL02() throws Exception {
|
||||
doTestEncodeURL(".", ".;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL03() throws Exception {
|
||||
doTestEncodeURL("..", "..;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL04() throws Exception {
|
||||
doTestEncodeURL(".././..", ".././..;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
public void testEncodeURL05() throws Exception {
|
||||
doTestEncodeURL("../../..", "../../..");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL06() throws Exception {
|
||||
doTestEncodeURL("bar.html", "bar.html;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL07() throws Exception {
|
||||
doTestEncodeURL("bar.html?x=/../", "bar.html;jsessionid=1234?x=/../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL08() throws Exception {
|
||||
doTestEncodeURL("bar.html?x=/../../", "bar.html;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL09() throws Exception {
|
||||
doTestEncodeURL("./.?x=/../../", "./.;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL10() throws Exception {
|
||||
doTestEncodeURL("./..?x=/../../", "./..;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL11() throws Exception {
|
||||
doTestEncodeURL("./..?x=/../..", "./..;jsessionid=1234?x=/../..");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL12() throws Exception {
|
||||
doTestEncodeURL("bar.html#/../", "bar.html;jsessionid=1234#/../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL13() throws Exception {
|
||||
doTestEncodeURL("bar.html#/../../", "bar.html;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL14() throws Exception {
|
||||
doTestEncodeURL("./.#/../../", "./.;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL15() throws Exception {
|
||||
doTestEncodeURL("./..#/../../", "./..;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeURL16() throws Exception {
|
||||
doTestEncodeURL("./..#/../..", "./..;jsessionid=1234#/../..");
|
||||
}
|
||||
|
||||
|
||||
private void doTestEncodeRedirectURL(String location, String expected) {
|
||||
Request req = new TesterRequest(true);
|
||||
req.setRequestedSessionId("1234");
|
||||
req.setRequestedSessionURL(true);
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.encodeRedirectURL(location);
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL01() throws Exception {
|
||||
doTestEncodeRedirectURL("./bar.html", "./bar.html;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL02() throws Exception {
|
||||
doTestEncodeRedirectURL(".", ".;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL03() throws Exception {
|
||||
doTestEncodeRedirectURL("..", "..;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL04() throws Exception {
|
||||
doTestEncodeRedirectURL(".././..", ".././..;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testEncodeRedirectURL05() throws Exception {
|
||||
doTestEncodeRedirectURL("../../..", "throws IAE");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL06() throws Exception {
|
||||
doTestEncodeRedirectURL("bar.html", "bar.html;jsessionid=1234");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL07() throws Exception {
|
||||
doTestEncodeRedirectURL("bar.html?x=/../", "bar.html;jsessionid=1234?x=/../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL08() throws Exception {
|
||||
doTestEncodeRedirectURL("bar.html?x=/../../", "bar.html;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL09() throws Exception {
|
||||
doTestEncodeRedirectURL("./.?x=/../../", "./.;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL10() throws Exception {
|
||||
doTestEncodeRedirectURL("./..?x=/../../", "./..;jsessionid=1234?x=/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL11() throws Exception {
|
||||
doTestEncodeRedirectURL("./..?x=/../..", "./..;jsessionid=1234?x=/../..");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL12() throws Exception {
|
||||
doTestEncodeRedirectURL("bar.html#/../", "bar.html;jsessionid=1234#/../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL13() throws Exception {
|
||||
doTestEncodeRedirectURL("bar.html#/../../", "bar.html;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL14() throws Exception {
|
||||
doTestEncodeRedirectURL("./.#/../../", "./.;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL15() throws Exception {
|
||||
doTestEncodeRedirectURL("./..#/../../", "./..;jsessionid=1234#/../../");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEncodeRedirectURL16() throws Exception {
|
||||
doTestEncodeURL("./..#/../..", "./..;jsessionid=1234#/../..");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendRedirect01() throws Exception {
|
||||
doTestSendRedirect("../foo", "../foo");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendRedirect02() throws Exception {
|
||||
doTestSendRedirect("../foo bar", "../foo bar");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendRedirect03() throws Exception {
|
||||
doTestSendRedirect("../foo%20bar", "../foo%20bar");
|
||||
}
|
||||
|
||||
|
||||
private void doTestSendRedirect(String input, String expectedLocation) throws Exception {
|
||||
// Set-up.
|
||||
// Note: Not sufficient for testing relative -> absolute
|
||||
Connector connector = new Connector();
|
||||
org.apache.coyote.Response cResponse = new org.apache.coyote.Response();
|
||||
Response response = new Response();
|
||||
response.setConnector(connector);
|
||||
response.setCoyoteResponse(cResponse);
|
||||
Request request = new Request();
|
||||
org.apache.coyote.Request cRequest = new org.apache.coyote.Request();
|
||||
request.setCoyoteRequest(cRequest);
|
||||
Context context = new TesterContext();
|
||||
request.getMappingData().context = context;
|
||||
response.setRequest(request);
|
||||
// Do test
|
||||
response.sendRedirect(input);
|
||||
String location = response.getHeader("Location");
|
||||
Assert.assertEquals(expectedLocation, location);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53469a() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.encodeURL("../bar.html");
|
||||
|
||||
Assert.assertEquals("../bar.html", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53469b() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
String result = resp.encodeURL("../../../../bar.html");
|
||||
|
||||
Assert.assertEquals("../../../../bar.html", result);
|
||||
}
|
||||
|
||||
|
||||
private static final class Bug52811Servlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("multipart/related;" +
|
||||
"boundary=1_4F50BD36_CDF8C28;" +
|
||||
"Start=\"<31671603.smil>\";" +
|
||||
"Type=\"application/smil;charset=UTF-8\"");
|
||||
|
||||
// Should be ISO-8859-1 because the charset in the above is part
|
||||
// of the Type parameter
|
||||
PrintWriter pw = resp.getWriter();
|
||||
if (resp.getCharacterEncoding().equals("ISO-8859-1")) {
|
||||
pw.print("OK");
|
||||
} else {
|
||||
pw.print("FAIL: " + resp.getCharacterEncoding());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.startup.LoggingBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterRequest;
|
||||
|
||||
public class TestResponsePerformance extends LoggingBaseTest {
|
||||
|
||||
private final int ITERATIONS = 100000;
|
||||
|
||||
@Test
|
||||
public void testToAbsolutePerformance() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
// Warm up
|
||||
doHomebrew(resp);
|
||||
doUri();
|
||||
|
||||
// Note: Java 9 on my OSX laptop consistently shows doUri() is faster
|
||||
// than doHomebrew(). Worth a closer look for Tomcat 10 on the
|
||||
// assumption it will require java 9
|
||||
|
||||
// To allow for timing differences between runs, a "best of n" approach
|
||||
// is taken for this test
|
||||
final int bestOf = 5;
|
||||
final int winTarget = (bestOf + 1) / 2;
|
||||
int homebrewWin = 0;
|
||||
int count = 0;
|
||||
|
||||
while (count < bestOf && homebrewWin < winTarget) {
|
||||
long homebrew = doHomebrew(resp);
|
||||
long uri = doUri();
|
||||
log.info("Current 'home-brew': " + homebrew + "ms, Using URI: " + uri + "ms");
|
||||
if (homebrew < uri) {
|
||||
homebrewWin++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
Assert.assertTrue(homebrewWin == winTarget);
|
||||
}
|
||||
|
||||
|
||||
private long doHomebrew(Response resp) {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
resp.toAbsolute("bar.html");
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
|
||||
private long doUri() {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
URI base = URI.create(
|
||||
"http://localhost:8080/level1/level2/foo.html");
|
||||
base.resolve(URI.create("bar.html")).toASCIIString();
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
}
|
||||
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Globals;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestSendFile extends TomcatBaseTest {
|
||||
|
||||
public static final int ITERATIONS = 10;
|
||||
public static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
@Test
|
||||
public void testSendFile() throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
File[] files = new File[ITERATIONS];
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
files[i] = generateFile(TEMP_DIR, "-" + i, EXPECTED_CONTENT_LENGTH * (i + 1));
|
||||
}
|
||||
try {
|
||||
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
WritingServlet servlet = new WritingServlet(files[i]);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String, List<String>> respHeaders = new HashMap<>();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
long start = System.currentTimeMillis();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/servlet" + i, bc, null,
|
||||
respHeaders);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
System.out.println("Client received " + bc.getLength() + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
Assert.assertEquals(EXPECTED_CONTENT_LENGTH * (i + 1L), bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
} finally {
|
||||
for (File f : files) {
|
||||
Assert.assertTrue("Failed to clean up [" + f + "]", f.delete());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public File generateFile(String dir, String suffix, int size) throws IOException {
|
||||
String name = "testSendFile-" + System.currentTimeMillis() + suffix + ".txt";
|
||||
File f = new File(dir, name);
|
||||
try (FileWriter fw = new FileWriter(f, false); BufferedWriter w = new BufferedWriter(fw);) {
|
||||
int defSize = 8192;
|
||||
while (size > 0) {
|
||||
int bytes = Math.min(size, defSize);
|
||||
char[] b = new char[bytes];
|
||||
Arrays.fill(b, 'X');
|
||||
w.write(b);
|
||||
size = size - bytes;
|
||||
}
|
||||
w.flush();
|
||||
}
|
||||
System.out
|
||||
.println("Created file:" + f.getAbsolutePath() + " with " + f.length() + " bytes.");
|
||||
return f;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final File f;
|
||||
|
||||
public WritingServlet(File f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(f.length());
|
||||
if (Boolean.TRUE.equals(req.getAttribute(Globals.SENDFILE_SUPPORTED_ATTR))) {
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, f.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(f.length()));
|
||||
} else {
|
||||
byte[] c = new byte[8192];
|
||||
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(f))) {
|
||||
int len = 0;
|
||||
int written = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
do {
|
||||
len = in.read(c);
|
||||
if (len > 0) {
|
||||
resp.getOutputStream().write(c, 0, len);
|
||||
written += len;
|
||||
}
|
||||
} while (len > 0);
|
||||
System.out.println("Server Wrote " + written + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60409() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctx = tomcat.addContext("", TEMP_DIR);
|
||||
File file = generateFile(TEMP_DIR, "", EXPECTED_CONTENT_LENGTH);
|
||||
Tomcat.addServlet(ctx, "test", new Bug60409Servlet(file));
|
||||
ctx.addServletMappingDecoded("/", "test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test/?" + Globals.SENDFILE_SUPPORTED_ATTR
|
||||
+ "=true", bc, null);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<Throwable> exceptions = new ArrayList<>();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
|
||||
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (exceptions.size() > 0) {
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Bug60409Servlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final File file;
|
||||
|
||||
Bug60409Servlet(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
if (Boolean.valueOf(req.getParameter(Globals.SENDFILE_SUPPORTED_ATTR)).booleanValue()) {
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(file.length());
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, file.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(file.length()));
|
||||
if (!file.delete()) {
|
||||
throw new ServletException("Failed to delete [" + file + "]");
|
||||
}
|
||||
} else {
|
||||
byte[] c = new byte[1024];
|
||||
Random rd = new Random();
|
||||
rd.nextBytes(c);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
resp.getOutputStream().write(c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RequestExecutor implements Runnable {
|
||||
private final String url;
|
||||
private final CountDownLatch latch;
|
||||
private final List<Throwable> exceptions;
|
||||
|
||||
RequestExecutor(String url, CountDownLatch latch, List<Throwable> exceptions) {
|
||||
this.url = url;
|
||||
this.latch = latch;
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ByteChunk result = new ByteChunk();
|
||||
int rc = getUrl(url, result, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(1024, result.getLength());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
exceptions.add(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
test/org/apache/catalina/connector/test_content.txt
Normal file
19
test/org/apache/catalina/connector/test_content.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This is a test file for the WebappServiceLoader
|
||||
# It contains comment lines and blank lines
|
||||
|
||||
test content
|
||||
Reference in New Issue
Block a user