414 lines
13 KiB
Java
414 lines
13 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|
|
}
|