This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View 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);
}
}
}

View 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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View 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()));
}
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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]);
}
}
}
}

View 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();
}
}
}

View 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());
}
}
}
}

View File

@@ -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;
}
}

View 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();
}
}
}
}

View 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