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,508 @@
/*
* 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.valves;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.Test;
/**
* Some simple micro-benchmarks to help determine best approach for thread
* safety in valves, particularly the {@link AccessLogValve}. Implemented as
* JUnit tests to make the simple to execute but does not used Test* as the
* class name to avoid being included in the automated unit tests.
*/
public class Benchmarks {
@Test
public void testAccessLogGetDate() throws Exception {
// Is it better to use a sync or a thread local here?
BenchmarkTest benchmark = new BenchmarkTest();
Runnable[] tests = new Runnable[] { new GetDateBenchmarkTest_Sync(),
new GetDateBenchmarkTest_Local(),
new GetDateBenchmarkTest_LocalMutableLong(),
new GetDateBenchmarkTest_LocalStruct() };
benchmark.doTest(5, tests);
}
private static class GetDateBenchmarkTest_Sync implements Runnable {
@Override
public String toString() {
return "Syncs";
}
private volatile long currentMillis = 0;
private volatile Date currentDate = null;
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillis) > 1000) {
synchronized (this) {
if ((systime - currentMillis) > 1000) {
currentDate = new Date(systime);
currentMillis = systime;
}
}
}
return currentDate;
}
}
private static class GetDateBenchmarkTest_Local implements Runnable {
@Override
public String toString() {
return "ThreadLocals";
}
private ThreadLocal<Long> currentMillisLocal = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return Long.valueOf(0);
}
};
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>();
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillisLocal.get().longValue()) > 1000) {
currentDateLocal.set(new Date(systime));
currentMillisLocal.set(Long.valueOf(systime));
}
return currentDateLocal.get();
}
}
private static class GetDateBenchmarkTest_LocalMutableLong implements
Runnable {
@Override
public String toString() {
return "ThreadLocals with a mutable Long";
}
private static class MutableLong {
long value = 0;
}
private ThreadLocal<MutableLong> currentMillisLocal = new ThreadLocal<MutableLong>() {
@Override
protected MutableLong initialValue() {
return new MutableLong();
}
};
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>();
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillisLocal.get().value) > 1000) {
currentDateLocal.set(new Date(systime));
currentMillisLocal.get().value = systime;
}
return currentDateLocal.get();
}
}
private static class GetDateBenchmarkTest_LocalStruct implements Runnable {
@Override
public String toString() {
return "single ThreadLocal";
}
// note, that we can avoid (long -> Long) conversion
private static class Struct {
public long currentMillis = 0;
public Date currentDate;
}
private ThreadLocal<Struct> currentStruct = new ThreadLocal<Struct>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
Struct struct = currentStruct.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentMillis) > 1000) {
struct.currentDate = new Date(systime);
struct.currentMillis = systime;
}
return struct.currentDate;
}
}
@Test
public void testAccessLogTimeDateElement() throws Exception {
// Is it better to use a sync or a thread local here?
BenchmarkTest benchmark = new BenchmarkTest();
Runnable[] tests = new Runnable[] {
new TimeDateElementBenchmarkTest_Sync(),
new TimeDateElementBenchmarkTest_Local(),
new TimeDateElementBenchmarkTest_LocalStruct(),
new TimeDateElementBenchmarkTest_LocalStruct_SBuilder() };
benchmark.doTest(5, tests);
}
private abstract static class TimeDateElementBenchmarkTestBase {
protected static final String months[] = { "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
protected String lookup(String month) {
int index;
try {
index = Integer.parseInt(month) - 1;
} catch (Throwable t) {
index = 0; // cannot happen, in theory
}
return (months[index]);
}
}
private static class TimeDateElementBenchmarkTest_Sync extends
TimeDateElementBenchmarkTestBase implements Runnable {
@Override
public String toString() {
return "Syncs";
}
private volatile Date currentDate = new Date();
private volatile String currentDateString = null;
private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
private SimpleDateFormat timeFormatter = new SimpleDateFormat(
"hh:mm:ss");
@Override
public void run() {
printDate();
}
public String printDate() {
StringBuilder buf = new StringBuilder();
Date date = getDateSync();
if (currentDate != date) {
synchronized (this) {
if (currentDate != date) {
StringBuilder current = new StringBuilder(32);
current.append('[');
current.append(dayFormatter.format(date)); // Day
current.append('/');
current.append(lookup(monthFormatter.format(date))); // Month
current.append('/');
current.append(yearFormatter.format(date)); // Year
current.append(':');
current.append(timeFormatter.format(date)); // Time
current.append(']');
currentDateString = current.toString();
currentDate = date;
}
}
}
buf.append(currentDateString);
return buf.toString();
}
private Date getDateSync() {
long systime = System.currentTimeMillis();
if ((systime - currentDate.getTime()) > 1000) {
synchronized (this) {
if ((systime - currentDate.getTime()) > 1000) {
currentDate.setTime(systime);
}
}
}
return currentDate;
}
}
private static class TimeDateElementBenchmarkTest_Local extends
TimeDateElementBenchmarkTestBase implements Runnable {
@Override
public String toString() {
return "ThreadLocals";
}
private ThreadLocal<String> currentDateStringLocal = new ThreadLocal<>();
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<Date>() {
@Override
protected Date initialValue() {
return new Date();
}
};
private ThreadLocal<SimpleDateFormat> dayFormatterLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd");
}
};
private ThreadLocal<SimpleDateFormat> monthFormatterLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MM");
}
};
private ThreadLocal<SimpleDateFormat> yearFormatterLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy");
}
};
private ThreadLocal<SimpleDateFormat> timeFormatterLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("hh:mm:ss");
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
if (currentDateStringLocal.get() == null) {
StringBuilder current = new StringBuilder(32);
current.append('[');
current.append(dayFormatterLocal.get().format(
currentDateLocal.get())); // Day
current.append('/');
current.append(lookup(monthFormatterLocal.get().format(
currentDateLocal.get()))); // Month
current.append('/');
current.append(yearFormatterLocal.get().format(
currentDateLocal.get())); // Year
current.append(':');
current.append(timeFormatterLocal.get().format(
currentDateLocal.get())); // Time
current.append(']');
currentDateStringLocal.set(current.toString());
}
return currentDateStringLocal.get();
}
private Date getDateLocal() {
long systime = System.currentTimeMillis();
if ((systime - currentDateLocal.get().getTime()) > 1000) {
currentDateLocal.get().setTime(systime);
currentDateStringLocal.set(null);
}
return currentDateLocal.get();
}
}
private static class TimeDateElementBenchmarkTest_LocalStruct extends
TimeDateElementBenchmarkTestBase implements Runnable {
@Override
public String toString() {
return "single ThreadLocal";
}
private static class Struct {
public String currentDateString;
public Date currentDate = new Date();
public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
public SimpleDateFormat timeFormatter = new SimpleDateFormat(
"hh:mm:ss");
}
private ThreadLocal<Struct> structLocal = new ThreadLocal<Struct>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
Struct struct = structLocal.get();
if (struct.currentDateString == null) {
StringBuilder current = new StringBuilder(32);
current.append('[');
current.append(struct.dayFormatter.format(struct.currentDate)); // Day
current.append('/');
current.append(lookup(struct.monthFormatter
.format(struct.currentDate))); // Month
current.append('/');
current.append(struct.yearFormatter.format(struct.currentDate)); // Year
current.append(':');
current.append(struct.timeFormatter.format(struct.currentDate)); // Time
current.append(']');
struct.currentDateString = current.toString();
}
return struct.currentDateString;
}
private Date getDateLocal() {
Struct struct = structLocal.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentDate.getTime()) > 1000) {
struct.currentDate.setTime(systime);
struct.currentDateString = null;
}
return struct.currentDate;
}
}
private static class TimeDateElementBenchmarkTest_LocalStruct_SBuilder extends
TimeDateElementBenchmarkTestBase implements Runnable {
@Override
public String toString() {
return "single ThreadLocal, with StringBuilder";
}
private static class Struct {
public String currentDateString;
public Date currentDate = new Date();
public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
public SimpleDateFormat timeFormatter = new SimpleDateFormat(
"hh:mm:ss");
}
private ThreadLocal<Struct> structLocal = new ThreadLocal<Struct>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
Struct struct = structLocal.get();
if (struct.currentDateString == null) {
StringBuilder current = new StringBuilder(32);
current.append('[');
current.append(struct.dayFormatter.format(struct.currentDate)); // Day
current.append('/');
current.append(lookup(struct.monthFormatter
.format(struct.currentDate))); // Month
current.append('/');
current.append(struct.yearFormatter.format(struct.currentDate)); // Year
current.append(':');
current.append(struct.timeFormatter.format(struct.currentDate)); // Time
current.append(']');
struct.currentDateString = current.toString();
}
return struct.currentDateString;
}
private Date getDateLocal() {
Struct struct = structLocal.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentDate.getTime()) > 1000) {
struct.currentDate.setTime(systime);
struct.currentDateString = null;
}
return struct.currentDate;
}
}
private static class BenchmarkTest {
public void doTest(int threadCount, Runnable[] tests) throws Exception {
for (int iterations = 1000000; iterations < 10000001; iterations += 1000000) {
for (int i = 0; i < tests.length; i++) {
doTestInternal(threadCount, iterations, tests[i]);
}
}
}
private void doTestInternal(int threadCount, int iterations,
Runnable test) throws Exception {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(new TestThread(iterations, test));
}
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
for (int i = 0; i < threadCount; i++) {
threads[i].join();
}
long end = System.currentTimeMillis();
System.out.println(test.getClass().getSimpleName() + ": "
+ threadCount + " threads and " + iterations
+ " iterations using " + test + " took " + (end - start)
+ "ms");
}
}
private static class TestThread implements Runnable {
private int count;
private Runnable test;
public TestThread(int count, Runnable test) {
this.count = count;
this.test = test;
}
@Override
public void run() {
for (int i = 0; i < count; i++) {
test.run();
}
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.valves;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.Assert;
import org.junit.Test;
public class TestAccessLogValve {
// Note that there is a similar test:
// org.apache.juli.TestDateFormatCache.testBug54044()
@Test
public void testBug54044() throws Exception {
final int cacheSize = 10;
SimpleDateFormat sdf =
new SimpleDateFormat("[dd/MMM/yyyy:HH:mm:ss Z]", Locale.US);
sdf.setTimeZone(TimeZone.getDefault());
AccessLogValve.DateFormatCache dfc =
new AccessLogValve.DateFormatCache(
cacheSize, Locale.US, null);
// Create an array to hold the expected values
String[] expected = new String[cacheSize];
// Fill the cache & populate the expected values
for (int secs = 0; secs < (cacheSize); secs++) {
dfc.getFormat(secs * 1000);
expected[secs] = generateExpected(sdf, secs);
}
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Cause the cache to roll-around by one and then confirm
dfc.getFormat(cacheSize * 1000);
expected[0] = generateExpected(sdf, cacheSize);
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Jump 2 ahead and then confirm (skipped value should be null)
dfc.getFormat((cacheSize + 2)* 1000);
expected[1] = null;
expected[2] = generateExpected(sdf, cacheSize + 2);
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Back 1 to fill in the gap
dfc.getFormat((cacheSize + 1)* 1000);
expected[1] = generateExpected(sdf, cacheSize + 1);
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Return to 1 and confirm skipped value is null
dfc.getFormat(1 * 1000);
expected[1] = generateExpected(sdf, 1);
expected[2] = null;
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Go back one further
dfc.getFormat(0);
expected[0] = generateExpected(sdf, 0);
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
// Jump ahead far enough that the entire cache will need to be cleared
dfc.getFormat(42 * 1000);
for (int i = 0; i < cacheSize; i++) {
expected[i] = null;
}
expected[0] = generateExpected(sdf, 42);
Assert.assertArrayEquals(expected, dfc.cLFCache.cache);
}
private String generateExpected(SimpleDateFormat sdf, long secs) {
return sdf.format(new Date(secs * 1000));
}
}

View File

@@ -0,0 +1,213 @@
/*
* 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.valves;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingListener;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Manager;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.session.StandardSession;
import org.easymock.EasyMock;
import org.easymock.IExpectationSetters;
public class TestCrawlerSessionManagerValve {
private static final Manager TEST_MANAGER;
static {
TEST_MANAGER = new StandardManager();
TEST_MANAGER.setContext(new StandardContext());
}
@Test
public void testCrawlerIpsPositive() throws Exception {
CrawlerSessionManagerValve valve = new CrawlerSessionManagerValve();
valve.setCrawlerIps("216\\.58\\.206\\.174");
valve.setCrawlerUserAgents(valve.getCrawlerUserAgents());
valve.setNext(EasyMock.createMock(Valve.class));
HttpSession session = createSessionExpectations(valve, true);
Request request = createRequestExpectations("216.58.206.174", session, true);
EasyMock.replay(request, session);
valve.invoke(request, EasyMock.createMock(Response.class));
EasyMock.verify(request, session);
}
@Test
public void testCrawlerIpsNegative() throws Exception {
CrawlerSessionManagerValve valve = new CrawlerSessionManagerValve();
valve.setCrawlerIps("216\\.58\\.206\\.174");
valve.setCrawlerUserAgents(valve.getCrawlerUserAgents());
valve.setNext(EasyMock.createMock(Valve.class));
HttpSession session = createSessionExpectations(valve, false);
Request request = createRequestExpectations("127.0.0.1", session, false);
EasyMock.replay(request, session);
valve.invoke(request, EasyMock.createMock(Response.class));
EasyMock.verify(request, session);
}
@Test
public void testCrawlerMultipleHostsHostAware() throws Exception {
CrawlerSessionManagerValve valve = new CrawlerSessionManagerValve();
valve.setCrawlerUserAgents(valve.getCrawlerUserAgents());
valve.setHostAware(true);
valve.setContextAware(true);
valve.setNext(EasyMock.createMock(Valve.class));
verifyCrawlingLocalhost(valve, "localhost");
verifyCrawlingLocalhost(valve, "example.invalid");
}
@Test
public void testCrawlerMultipleContextsContextAware() throws Exception {
CrawlerSessionManagerValve valve = new CrawlerSessionManagerValve();
valve.setCrawlerUserAgents(valve.getCrawlerUserAgents());
valve.setHostAware(true);
valve.setContextAware(true);
valve.setNext(EasyMock.createMock(Valve.class));
verifyCrawlingContext(valve, "/examples");
verifyCrawlingContext(valve, null);
}
@Test
public void testCrawlersSessionIdIsRemovedAfterSessionExpiry() throws IOException, ServletException {
CrawlerSessionManagerValve valve = new CrawlerSessionManagerValve();
valve.setCrawlerIps("216\\.58\\.206\\.174");
valve.setCrawlerUserAgents(valve.getCrawlerUserAgents());
valve.setNext(EasyMock.createMock(Valve.class));
valve.setSessionInactiveInterval(0);
StandardSession session = new StandardSession(TEST_MANAGER);
session.setId("id");
session.setValid(true);
Request request = createRequestExpectations("216.58.206.174", session, true);
EasyMock.replay(request);
valve.invoke(request, EasyMock.createMock(Response.class));
EasyMock.verify(request);
MatcherAssert.assertThat(valve.getClientIpSessionId().values(), CoreMatchers.hasItem("id"));
session.expire();
Assert.assertEquals(0, valve.getClientIpSessionId().values().size());
}
private void verifyCrawlingLocalhost(CrawlerSessionManagerValve valve, String hostname)
throws IOException, ServletException {
HttpSession session = createSessionExpectations(valve, true);
Request request = createRequestExpectations("127.0.0.1", session, true, hostname, "/examples", "tomcatBot 1.0");
EasyMock.replay(request, session);
valve.invoke(request, EasyMock.createMock(Response.class));
EasyMock.verify(request, session);
}
private void verifyCrawlingContext(CrawlerSessionManagerValve valve, String contextPath)
throws IOException, ServletException {
HttpSession session = createSessionExpectations(valve, true);
Request request = createRequestExpectations("127.0.0.1", session, true, "localhost", contextPath, "tomcatBot 1.0");
EasyMock.replay(request, session);
valve.invoke(request, EasyMock.createMock(Response.class));
EasyMock.verify(request, session);
}
private HttpSession createSessionExpectations(CrawlerSessionManagerValve valve, boolean isBot) {
HttpSession session = EasyMock.createMock(HttpSession.class);
if (isBot) {
EasyMock.expect(session.getId()).andReturn("id").times(2);
session.setAttribute(EasyMock.eq(valve.getClass().getName()), EasyMock.anyObject(HttpSessionBindingListener.class));
EasyMock.expectLastCall();
session.setMaxInactiveInterval(60);
EasyMock.expectLastCall();
}
return session;
}
private Request createRequestExpectations(String ip, HttpSession session, boolean isBot) {
return createRequestExpectations(ip, session, isBot, "localhost", "/examples", "something 1.0");
}
private Request createRequestExpectations(String ip, HttpSession session, boolean isBot, String hostname,
String contextPath, String userAgent) {
Request request = EasyMock.createMock(Request.class);
EasyMock.expect(request.getRemoteAddr()).andReturn(ip);
EasyMock.expect(request.getHost()).andReturn(simpleHostWithName(hostname));
EasyMock.expect(request.getContext()).andReturn(simpleContextWithName(contextPath));
IExpectationSetters<HttpSession> setter = EasyMock.expect(request.getSession(false))
.andReturn(null);
if (isBot) {
setter.andReturn(session);
}
EasyMock.expect(request.getHeaders("user-agent")).andReturn(Collections.enumeration(Arrays.asList(userAgent)));
return request;
}
private Host simpleHostWithName(String hostname) {
Host host = EasyMock.createMock(Host.class);
EasyMock.expect(host.getName()).andReturn(hostname);
EasyMock.replay(host);
return host;
}
private Context simpleContextWithName(String contextPath) {
if (contextPath == null) {
return null;
}
Context context = EasyMock.createMock(Context.class);
EasyMock.expect(context.getName()).andReturn(contextPath);
EasyMock.replay(context);
return context;
}
}

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.valves;
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.RequestDispatcher;
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.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
public class TestErrorReportValve extends TomcatBaseTest {
@Test
public void testBug53071() throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "errorServlet", new ErrorServlet());
ctx.addServletMappingDecoded("/", "errorServlet");
tomcat.start();
ByteChunk res = getUrl("http://localhost:" + getPort());
Assert.assertTrue(res.toString().contains("<p><b>Message</b> " +
ErrorServlet.ERROR_TEXT + "</p>"));
}
private static final class ErrorServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String ERROR_TEXT = "The wheels fell off.";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setAttribute(RequestDispatcher.ERROR_EXCEPTION,
new Throwable(ERROR_TEXT));
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@Test
public void testBug54220DoNotSetNotFound() throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "bug54220", new Bug54220Servlet(false));
ctx.addServletMappingDecoded("/", "bug54220");
tomcat.start();
ByteChunk res = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort(), res, null);
Assert.assertNull(res.toString());
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
}
@Test
public void testBug54220SetNotFound() throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "bug54220", new Bug54220Servlet(true));
ctx.addServletMappingDecoded("/", "bug54220");
tomcat.start();
ByteChunk res = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort(), res, null);
Assert.assertNull(res.toString());
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc);
}
private static final class Bug54220Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private boolean setNotFound;
private Bug54220Servlet(boolean setNotFound) {
this.setNotFound = setNotFound;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (setNotFound) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}
/*
* Custom error/status codes should not result in a blank response.
*/
@Test
public void testBug54536() throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "bug54536", new Bug54536Servlet());
ctx.addServletMappingDecoded("/", "bug54536");
tomcat.start();
ByteChunk res = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort(), res, null);
Assert.assertEquals(Bug54536Servlet.ERROR_STATUS, rc);
String body = res.toString();
Assert.assertNotNull(body);
Assert.assertTrue(body, body.contains(Bug54536Servlet.ERROR_MESSAGE));
}
private static final class Bug54536Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final int ERROR_STATUS = 999;
private static final String ERROR_MESSAGE = "The sky is falling";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.sendError(ERROR_STATUS, ERROR_MESSAGE);
}
}
@Test
public void testBug56042() throws Exception {
// Setup Tomcat instance
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Bug56042Servlet bug56042Servlet = new Bug56042Servlet();
Wrapper wrapper =
Tomcat.addServlet(ctx, "bug56042Servlet", bug56042Servlet);
wrapper.setAsyncSupported(true);
ctx.addServletMappingDecoded("/bug56042Servlet", "bug56042Servlet");
tomcat.start();
StringBuilder url = new StringBuilder(48);
url.append("http://localhost:");
url.append(getPort());
url.append("/bug56042Servlet");
ByteChunk res = new ByteChunk();
int rc = getUrl(url.toString(), res, null);
Assert.assertEquals(HttpServletResponse.SC_BAD_REQUEST, rc);
}
private static class Bug56042Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Only set the status on the first call (the dispatch will trigger
// another call to this Servlet)
if (resp.getStatus() != HttpServletResponse.SC_BAD_REQUEST) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
AsyncContext ac = req.startAsync();
ac.dispatch();
}
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.valves;
import org.junit.Assert;
import org.junit.Test;
public class TestExtendedAccessLogValve {
@Test
public void alpha() {
Assert.assertEquals("\"foo\"", ExtendedAccessLogValve.wrap("foo"));
}
@Test
public void testNull() {
Assert.assertEquals("-", ExtendedAccessLogValve.wrap(null));
}
@Test
public void empty() {
Assert.assertEquals("\"\"", ExtendedAccessLogValve.wrap(""));
}
@Test
public void singleQuoteMiddle() {
Assert.assertEquals("\"foo'bar\"", ExtendedAccessLogValve.wrap("foo'bar"));
}
@Test
public void doubleQuoteMiddle() {
Assert.assertEquals("\"foo\"\"bar\"", ExtendedAccessLogValve.wrap("foo\"bar"));
}
@Test
public void doubleQuoteStart() {
Assert.assertEquals("\"\"\"foobar\"", ExtendedAccessLogValve.wrap("\"foobar"));
}
@Test
public void doubleQuoteEnd() {
Assert.assertEquals("\"foobar\"\"\"", ExtendedAccessLogValve.wrap("foobar\""));
}
@Test
public void doubleQuote() {
Assert.assertEquals("\"\"\"\"", ExtendedAccessLogValve.wrap("\""));
}
}

View File

@@ -0,0 +1,275 @@
/* 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.valves;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.SessionCookieConfig;
import javax.servlet.http.Cookie;
import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardPipeline;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
public class TestLoadBalancerDrainingValve {
static class MockResponse extends Response {
private List<Cookie> cookies;
@Override
public boolean isCommitted() {
return false;
}
@Override
public void addCookie(Cookie cookie)
{
if(null == cookies)
cookies = new ArrayList<>(1);
cookies.add(cookie);
}
@Override
public List<Cookie> getCookies() {
return cookies;
}
}
static class CookieConfig implements SessionCookieConfig {
private String name;
private String domain;
private String path;
private String comment;
private boolean httpOnly;
private boolean secure;
private int maxAge;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getDomain() {
return domain;
}
@Override
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public String getPath() {
return path;
}
@Override
public void setPath(String path) {
this.path = path;
}
@Override
public String getComment() {
return comment;
}
@Override
public void setComment(String comment) {
this.comment = comment;
}
@Override
public boolean isHttpOnly() {
return httpOnly;
}
@Override
public void setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public void setSecure(boolean secure) {
this.secure = secure;
}
@Override
public int getMaxAge() {
return maxAge;
}
@Override
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
}
// A Cookie subclass that knows how to compare itself to other Cookie objects
static class MyCookie extends Cookie {
private static final long serialVersionUID = 1L;
public MyCookie(String name, String value) { super(name, value); }
@Override
public boolean equals(Object o) {
if(!(o instanceof MyCookie)) {
return false;
}
MyCookie mc = (MyCookie)o;
return mc.getName().equals(this.getName())
&& mc.getPath().equals(this.getPath())
&& mc.getValue().equals(this.getValue())
&& mc.getMaxAge() == this.getMaxAge();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getMaxAge();
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode());
result = prime * result + ((getValue() == null) ? 0 : getValue().hashCode());
return result;
}
@Override
public String toString() {
return "Cookie { name=" + getName() + ", value=" + getValue() + ", path=" + getPath() + ", maxAge=" + getMaxAge() + " }";
}
}
@Test
public void testNormalRequest() throws Exception {
runValve("ACT", true, true, false, null);
}
@Test
public void testDisabledValidSession() throws Exception {
runValve("DIS", true, true, false, null);
}
@Test
public void testDisabledInvalidSession() throws Exception {
runValve("DIS", false, false, false, "foo=bar");
}
@Test
public void testDisabledInvalidSessionWithIgnore() throws Exception {
runValve("DIS", false, true, true, "foo=bar");
}
private void runValve(String jkActivation,
boolean validSessionId,
boolean expectInvokeNext,
boolean enableIgnore,
String queryString) throws Exception {
IMocksControl control = EasyMock.createControl();
ServletContext servletContext = control.createMock(ServletContext.class);
Context ctx = control.createMock(Context.class);
Request request = control.createMock(Request.class);
Response response = control.createMock(Response.class);
String sessionCookieName = "JSESSIONID";
String sessionId = "cafebabe";
String requestURI = "/test/path";
SessionCookieConfig cookieConfig = new CookieConfig();
cookieConfig.setDomain("example.com");
cookieConfig.setName(sessionCookieName);
cookieConfig.setPath("/");
// Valve.init requires all of this stuff
EasyMock.expect(ctx.getMBeanKeyProperties()).andStubReturn("");
EasyMock.expect(ctx.getName()).andStubReturn("");
EasyMock.expect(ctx.getPipeline()).andStubReturn(new StandardPipeline());
EasyMock.expect(ctx.getDomain()).andStubReturn("foo");
EasyMock.expect(ctx.getLogger()).andStubReturn(org.apache.juli.logging.LogFactory.getLog(LoadBalancerDrainingValve.class));
EasyMock.expect(ctx.getServletContext()).andStubReturn(servletContext);
// Set up the actual test
EasyMock.expect(request.getAttribute(LoadBalancerDrainingValve.ATTRIBUTE_KEY_JK_LB_ACTIVATION)).andStubReturn(jkActivation);
EasyMock.expect(Boolean.valueOf(request.isRequestedSessionIdValid())).andStubReturn(Boolean.valueOf(validSessionId));
ArrayList<Cookie> cookies = new ArrayList<>();
if(enableIgnore) {
cookies.add(new Cookie("ignore", "true"));
}
if(!validSessionId) {
MyCookie cookie = new MyCookie(cookieConfig.getName(), sessionId);
cookie.setPath(cookieConfig.getPath());
cookie.setValue(sessionId);
cookies.add(cookie);
EasyMock.expect(request.getRequestedSessionId()).andStubReturn(sessionId);
EasyMock.expect(request.getRequestURI()).andStubReturn(requestURI);
EasyMock.expect(request.getCookies()).andStubReturn(cookies.toArray(new Cookie[cookies.size()]));
EasyMock.expect(request.getContext()).andStubReturn(ctx);
EasyMock.expect(ctx.getSessionCookieName()).andStubReturn(sessionCookieName);
EasyMock.expect(servletContext.getSessionCookieConfig()).andStubReturn(cookieConfig);
EasyMock.expect(request.getQueryString()).andStubReturn(queryString);
EasyMock.expect(ctx.getSessionCookiePath()).andStubReturn("/");
if (!enableIgnore) {
EasyMock.expect(Boolean.valueOf(ctx.getSessionCookiePathUsesTrailingSlash())).andStubReturn(Boolean.TRUE);
EasyMock.expect(request.getQueryString()).andStubReturn(queryString);
// Response will have cookie deleted
MyCookie expectedCookie = new MyCookie(cookieConfig.getName(), "");
expectedCookie.setPath(cookieConfig.getPath());
expectedCookie.setMaxAge(0);
// These two lines just mean EasyMock.expect(response.addCookie) but for a void method
response.addCookie(expectedCookie);
EasyMock.expect(ctx.getSessionCookieName()).andReturn(sessionCookieName); // Indirect call
String expectedRequestURI = requestURI;
if(null != queryString)
expectedRequestURI = expectedRequestURI + '?' + queryString;
response.setHeader("Location", expectedRequestURI);
response.setStatus(307);
}
}
Valve next = control.createMock(Valve.class);
if(expectInvokeNext) {
// Expect the "next" Valve to fire
// Next 2 lines are basically EasyMock.expect(next.invoke(req,res)) but for a void method
next.invoke(request, response);
EasyMock.expectLastCall();
}
// Get set to actually test
control.replay();
LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve();
valve.setContainer(ctx);
valve.init();
valve.setNext(next);
valve.setIgnoreCookieName("ignore");
valve.setIgnoreCookieValue("true");
valve.invoke(request, response);
control.verify();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,346 @@
/*
* 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.valves;
import java.io.IOException;
import javax.servlet.ServletException;
import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
/**
* {@link RequestFilterValve} Tests
*/
public class TestRequestFilterValve {
private static final int OK = 200;
private static final int FORBIDDEN = 403;
private static final int CUSTOM = 499;
private static final String ADDR_ALLOW_PAT = "127\\.\\d*\\.\\d*\\.\\d*";
private static final String ADDR_DENY_PAT = "\\d*\\.\\d*\\.\\d*\\.1";
private static final String ADDR_ONLY_ALLOW = "127.0.0.2";
private static final String ADDR_ONLY_DENY = "192.168.0.1";
private static final String ADDR_ALLOW_AND_DENY = "127.0.0.1";
private static final String ADDR_NO_ALLOW_NO_DENY = "192.168.0.2";
private static final String HOST_ALLOW_PAT = "www\\.example\\.[a-zA-Z0-9-]*";
private static final String HOST_DENY_PAT = ".*\\.org";
private static final String HOST_ONLY_ALLOW = "www.example.com";
private static final String HOST_ONLY_DENY = "host.example.org";
private static final String HOST_ALLOW_AND_DENY = "www.example.org";
private static final String HOST_NO_ALLOW_NO_DENY = "host.example.com";
private static final int PORT = 8080;
private static final String PORT_MATCH_PATTERN = ";\\d*";
private static final String PORT_NO_MATCH_PATTERN = ";8081";
static class TerminatingValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
}
}
public static class MockResponse extends Response {
private int status = OK;
@Override
public void sendError(int status) throws IOException {
this.status = status;
}
@Override
public int getStatus() {
return status;
}
}
private void oneTest(String allow, String deny, boolean denyStatus,
boolean addConnectorPort, boolean auth,
String property, String type, boolean allowed) {
// PREPARE
RequestFilterValve valve = null;
Connector connector = new Connector();
Context context = new StandardContext();
Request request = new Request();
Response response = new MockResponse();
StringBuilder msg = new StringBuilder();
int expected = allowed ? OK : FORBIDDEN;
connector.setPort(PORT);
request.setConnector(connector);
request.getMappingData().context = context;
request.setCoyoteRequest(new org.apache.coyote.Request());
Assert.assertNotNull("Invalid test with null type", type);
if (property != null) {
if (type.equals("Addr")) {
valve = new RemoteAddrValve();
request.setRemoteAddr(property);
msg.append(" ip='" + property + "'");
} else if (type.equals("Host")) {
valve = new RemoteHostValve();
request.setRemoteHost(property);
msg.append(" host='" + property + "'");
}
}
Assert.assertNotNull("Invalid test type" + type, valve);
valve.setNext(new TerminatingValve());
if (allow != null) {
valve.setAllow(allow);
msg.append(" allow='" + allow + "'");
}
if (deny != null) {
valve.setDeny(deny);
msg.append(" deny='" + deny + "'");
}
if (denyStatus) {
valve.setDenyStatus(CUSTOM);
msg.append(" denyStatus='" + CUSTOM + "'");
if (!allowed) {
expected = CUSTOM;
}
}
if (addConnectorPort) {
if (valve instanceof RemoteAddrValve) {
((RemoteAddrValve)valve).setAddConnectorPort(true);
} else if (valve instanceof RemoteHostValve) {
((RemoteHostValve)valve).setAddConnectorPort(true);
} else {
Assert.fail("Can only set 'addConnectorPort' for RemoteAddrValve and RemoteHostValve");
}
msg.append(" addConnectorPort='true'");
}
if (auth) {
context.setPreemptiveAuthentication(true);
valve.setInvalidAuthenticationWhenDeny(true);
msg.append(" auth='true'");
}
// TEST
try {
valve.invoke(request, response);
} catch (IOException ex) {
//Ignore
} catch (ServletException ex) {
//Ignore
}
// VERIFY
if (!allowed && auth) {
Assert.assertEquals(msg.toString(), OK, response.getStatus());
Assert.assertEquals(msg.toString(), "invalid", request.getHeader("authorization"));
} else {
Assert.assertEquals(msg.toString(), expected, response.getStatus());
}
}
private void standardTests(String allow_pat, String deny_pat,
String OnlyAllow, String OnlyDeny,
String AllowAndDeny, String NoAllowNoDeny,
boolean auth, String type) {
String apat;
String dpat;
// Test without ports
apat = allow_pat;
dpat = deny_pat;
oneTest(null, null, false, false, auth, AllowAndDeny, type, false);
oneTest(null, null, true, false, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, false, auth, AllowAndDeny, type, true);
oneTest(apat, null, false, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, false, auth, AllowAndDeny, type, true);
oneTest(apat, null, true, false, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, false, auth, AllowAndDeny, type, false);
oneTest(null, dpat, false, false, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, false, auth, AllowAndDeny, type, false);
oneTest(null, dpat, true, false, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, false, auth, OnlyAllow, type, true);
oneTest(apat, dpat, false, false, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, false, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, false, auth, OnlyAllow, type, true);
oneTest(apat, dpat, true, false, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, false, auth, AllowAndDeny, type, false);
// Test with port in pattern but forgotten "addConnectorPort"
apat = allow_pat + PORT_MATCH_PATTERN;
dpat = deny_pat + PORT_MATCH_PATTERN;
oneTest(null, null, false, false, auth, AllowAndDeny, type, false);
oneTest(null, null, true, false, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, false, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, false, auth, AllowAndDeny, type, false);
oneTest(apat, null, true, false, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, false, auth, AllowAndDeny, type, true);
oneTest(null, dpat, false, false, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, false, auth, AllowAndDeny, type, true);
oneTest(null, dpat, true, false, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, false, auth, OnlyAllow, type, false);
oneTest(apat, dpat, false, false, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, false, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, false, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, false, auth, OnlyAllow, type, false);
oneTest(apat, dpat, true, false, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, false, auth, AllowAndDeny, type, false);
// Test with "addConnectorPort" but port not in pattern
apat = allow_pat;
dpat = deny_pat;
oneTest(null, null, false, true, auth, AllowAndDeny, type, false);
oneTest(null, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, true, true, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, false, true, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, true, true, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, false, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, true, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, true, auth, AllowAndDeny, type, false);
// Test "addConnectorPort" and with port matching in both patterns
apat = allow_pat + PORT_MATCH_PATTERN;
dpat = deny_pat + PORT_MATCH_PATTERN;
oneTest(null, null, false, true, auth, AllowAndDeny, type, false);
oneTest(null, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, AllowAndDeny, type, true);
oneTest(apat, null, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, true, auth, AllowAndDeny, type, true);
oneTest(apat, null, true, true, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(null, dpat, false, true, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, true, auth, AllowAndDeny, type, false);
oneTest(null, dpat, true, true, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, true, auth, OnlyAllow, type, true);
oneTest(apat, dpat, false, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, true, auth, OnlyAllow, type, true);
oneTest(apat, dpat, true, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, true, auth, AllowAndDeny, type, false);
// Test "addConnectorPort" and with port not matching in both patterns
apat = allow_pat + PORT_NO_MATCH_PATTERN;
dpat = deny_pat + PORT_NO_MATCH_PATTERN;
oneTest(null, null, false, true, auth, AllowAndDeny, type, false);
oneTest(null, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, true, true, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, false, true, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, true, true, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, false, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, true, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, true, auth, AllowAndDeny, type, false);
// Test "addConnectorPort" and with port matching only in allow
apat = allow_pat + PORT_MATCH_PATTERN;
dpat = deny_pat + PORT_NO_MATCH_PATTERN;
oneTest(null, null, false, true, auth, AllowAndDeny, type, false);
oneTest(null, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, AllowAndDeny, type, true);
oneTest(apat, null, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, true, auth, AllowAndDeny, type, true);
oneTest(apat, null, true, true, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, false, true, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, true, auth, AllowAndDeny, type, true);
oneTest(null, dpat, true, true, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, true, auth, OnlyAllow, type, true);
oneTest(apat, dpat, false, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, true, auth, AllowAndDeny, type, true);
oneTest(apat, dpat, true, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, true, auth, OnlyAllow, type, true);
oneTest(apat, dpat, true, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, true, auth, AllowAndDeny, type, true);
// Test "addConnectorPort" and with port matching only in deny
apat = allow_pat + PORT_NO_MATCH_PATTERN;
dpat = deny_pat + PORT_MATCH_PATTERN;
oneTest(null, null, false, true, auth, AllowAndDeny, type, false);
oneTest(null, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, null, true, true, auth, AllowAndDeny, type, false);
oneTest(apat, null, true, true, auth, NoAllowNoDeny, type, false);
oneTest(null, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(null, dpat, false, true, auth, NoAllowNoDeny, type, true);
oneTest(null, dpat, true, true, auth, AllowAndDeny, type, false);
oneTest(null, dpat, true, true, auth, NoAllowNoDeny, type, true);
oneTest(apat, dpat, false, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, false, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, false, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, false, true, auth, AllowAndDeny, type, false);
oneTest(apat, dpat, true, true, auth, NoAllowNoDeny, type, false);
oneTest(apat, dpat, true, true, auth, OnlyAllow, type, false);
oneTest(apat, dpat, true, true, auth, OnlyDeny, type, false);
oneTest(apat, dpat, true, true, auth, AllowAndDeny, type, false);
}
@Test
public void testRemoteAddrValveIPv4() {
standardTests(ADDR_ALLOW_PAT, ADDR_DENY_PAT,
ADDR_ONLY_ALLOW, ADDR_ONLY_DENY,
ADDR_ALLOW_AND_DENY, ADDR_NO_ALLOW_NO_DENY,
false, "Addr");
standardTests(ADDR_ALLOW_PAT, ADDR_DENY_PAT,
ADDR_ONLY_ALLOW, ADDR_ONLY_DENY,
ADDR_ALLOW_AND_DENY, ADDR_NO_ALLOW_NO_DENY,
true, "Addr");
}
@Test
public void testRemoteHostValve() {
standardTests(HOST_ALLOW_PAT, HOST_DENY_PAT,
HOST_ONLY_ALLOW, HOST_ONLY_DENY,
HOST_ALLOW_AND_DENY, HOST_NO_ALLOW_NO_DENY,
false, "Host");
standardTests(HOST_ALLOW_PAT, HOST_DENY_PAT,
HOST_ONLY_ALLOW, HOST_ONLY_DENY,
HOST_ALLOW_AND_DENY, HOST_NO_ALLOW_NO_DENY,
true, "Host");
}
}

View File

@@ -0,0 +1,333 @@
/*
* 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.valves;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.logging.Level;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.apache.catalina.Globals;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.tomcat.unittest.TesterLogValidationFilter;
import org.easymock.EasyMock;
public class TestSSLValve {
public static class MockRequest extends Request {
public MockRequest() {
setConnector(EasyMock.createMock(Connector.class));
setCoyoteRequest(new org.apache.coyote.Request());
}
@Override
public void setAttribute(String name, Object value) {
getCoyoteRequest().getAttributes().put(name, value);
}
@Override
public Object getAttribute(String name) {
return getCoyoteRequest().getAttribute(name);
}
public void setHeader(String header, String value) {
getCoyoteRequest().getMimeHeaders().setValue(header).setString(value);
}
public void addHeader(String header, String value) {
getCoyoteRequest().getMimeHeaders().addValue(header).setString(value);
}
}
private static final String[] CERTIFICATE_LINES = new String[] { "-----BEGIN CERTIFICATE-----",
"MIIFXTCCA0WgAwIBAgIJANFf3YTJgYifMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV",
"BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX",
"aWRnaXRzIFB0eSBMdGQwHhcNMTcwNTI2MjEzNjM3WhcNMTgwNTI2MjEzNjM3WjBF",
"MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50",
"ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC",
"CgKCAgEA2ykNBanZz4cVITNpZcWNVmErUzqgSNrK361mj9vEdB1UkHatwal9jVrR",
"QvFgfiZ8Gl+/85t0ebJhJ+rIr1ww6JE7v2s2MThENj95K5EwZOmgvw+CBlBYsFIz",
"8BtjlVYy+v7RaGPXfjrFkexQP9UIaiIIog2ClDZirRvb+QxS930/YW5Qo+X6EX6W",
"/m/HvlorD25U4ni2FQ0y+EMO2e1jD88cAAMoP5f+Mf6NBK8I6yUeaSuMq7WqtHGV",
"e4F1WOg5z9J5c/M69rB0iQr5NUQwZ1mPYf5Kr0P6+TLh8DJphbVvmHJyT3bgofeV",
"JYl/kdjiXS5P/jwY9tfmhu04tsyzopWRUFCcj5zCiqZYaMn0wtDn08KaAh9oOlg8",
"Z6mJ9i5EybkLm63W7z7LxuM+qnYzq4wKkKdx8hbpASwPqzJkJeXFL/LzhKdZuHiR",
"clgPVYnm98URwhObh073dKguG/gkhcnpXcVBBVdVTJZYGBvTpQh0afXd9bcBwOzY",
"t4MDpGiQB2fLzBOEZhQ37kUcWPmZw5bNPxhx4yE96Md0rx/Gu4ipAHuqLemb1SL5",
"uWNesVmgY3OXaIamQIm9BCwkf8mMvoYdAT+lukTUZLtJ6s2w+Oxnl10tmb+6sTXy",
"UB3WcBTp/o3YjAyJPnM1Wq6nVNQ4W2+NbV5purGAP09sumxeJj8CAwEAAaNQME4w",
"HQYDVR0OBBYEFCGOYMvymUG2ZZT+lK4LvwEvx731MB8GA1UdIwQYMBaAFCGOYMvy",
"mUG2ZZT+lK4LvwEvx731MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB",
"AG6m4nDYCompUtRVude1qulwwAaYEMHyIIsfymI8uAE7d2o4bGjVpAUOdH/VWSOp",
"Rzx0oK6K9cHyiBlKHw5zSZdqcRi++tDX3P9Iy5tXO//zkhMEnSpk6RF2+9JXtyhx",
"Gma4yAET1yES+ybiFT21uZrGCC9r69rWG8JRZshc4RVWGwZsd0zrATVqfY0mZurm",
"xLgU4UOvkTczjlrLiklwwU68M1DLcILJ5FZGTWeTJ/q1wpIn9isK2siAW/VOcbuG",
"xdbGladnIFv+iQfuZG0yjcuMsBFsQiXi6ONM8GM+dr+61V63/1s73jYcOToEsTMM",
"3bHeVffoSkhZvOGTRCI6QhK9wqnIKhAYqu+NbV4OphfE3gOaK+T1cASXUtSQPXoa",
"sEoIVmbQsWRBhWvYShVqvINsH/hAT3Cf/+SslprtQUqiyt2ljdgrRFZdoyB3S7ky",
"KWoZRvHRj2cKU65LVYwx6U1A8SGmViz4aHMSai0wwKzOVv9MGHeRaVhlMmMsbdfu",
"wKoKJv0xYoVwEh1rB8TH8PjbL+6eFLeZXYVZnH71d5JHCghZ8W0a11ZuYkscSQWk",
"yoTBqEpJloWksrypqp3iL4PAL5+KkB2zp66+MVAg8LcEDFJggBBJCtv4SCWV7ZOB",
"WLu8gep+XCwSn0Wb6D3eFs4DoIiMvQ6g2rS/pk7o5eWj", "-----END CERTIFICATE-----" };
private SSLValve valve = new SSLValve();
private MockRequest mockRequest = new MockRequest();
private Valve mockNext = EasyMock.createMock(Valve.class);
@Before
public void setUp() throws Exception {
valve.setNext(mockNext);
mockNext.invoke(mockRequest, null);
EasyMock.replay(mockNext);
}
@Test
public void testSslHeader() {
final String headerName = "myheader";
final String headerValue = "BASE64_HEADER_VALUE";
mockRequest.setHeader(headerName, headerValue);
Assert.assertEquals(headerValue, valve.mygetHeader(mockRequest, headerName));
}
@Test
public void testSslHeaderNull() {
final String headerName = "myheader";
mockRequest.setHeader(headerName, null);
Assert.assertNull(valve.mygetHeader(mockRequest, headerName));
}
@Test
public void testSslHeaderNullModHeader() {
final String headerName = "myheader";
final String nullModHeaderValue = "(null)";
mockRequest.setHeader(headerName, nullModHeaderValue);
Assert.assertNull(valve.mygetHeader(mockRequest, nullModHeaderValue));
}
@Test
public void testSslHeaderNullName() throws Exception {
Assert.assertNull(valve.mygetHeader(mockRequest, null));
}
@Test
public void testSslHeaderMultiples() throws Exception {
final String headerName = "myheader";
final String headerValue = "BASE64_HEADER_VALUE";
mockRequest.addHeader(headerName, headerValue);
mockRequest.addHeader(headerName, "anyway won't be found");
Assert.assertEquals(headerValue, valve.mygetHeader(mockRequest, headerName));
}
@Test
public void testSslClientCertHeaderSingleSpace() throws Exception {
String singleSpaced = certificateSingleLine(" ");
mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced);
valve.invoke(mockRequest, null);
assertCertificateParsed();
}
@Test
public void testSslClientCertHeaderMultiSpace() throws Exception {
String singleSpaced = certificateSingleLine(" ");
mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced);
valve.invoke(mockRequest, null);
assertCertificateParsed();
}
@Test
public void testSslClientCertHeaderTab() throws Exception {
String singleSpaced = certificateSingleLine("\t");
mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced);
valve.invoke(mockRequest, null);
assertCertificateParsed();
}
@Test
public void testSslClientCertNull() throws Exception {
TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null,
"org.apache.catalina.valves.SSLValve");
valve.invoke(mockRequest, null);
EasyMock.verify(mockNext);
Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR));
Assert.assertEquals(0, f.getMessageCount());
}
@Test
public void testSslClientCertShorter() throws Exception {
mockRequest.setHeader(valve.getSslClientCertHeader(), "shorter than hell");
TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null,
"org.apache.catalina.valves.SSLValve");
valve.invoke(mockRequest, null);
EasyMock.verify(mockNext);
Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR));
Assert.assertEquals(0, f.getMessageCount());
}
@Test
public void testSslClientCertIgnoredBegin() throws Exception {
String[] linesBegin = Arrays.copyOf(CERTIFICATE_LINES, CERTIFICATE_LINES.length);
linesBegin[0] = "3fisjcme3kdsakasdfsadkafsd3";
String begin = certificateSingleLine(linesBegin, " ");
mockRequest.setHeader(valve.getSslClientCertHeader(), begin);
valve.invoke(mockRequest, null);
assertCertificateParsed();
}
@Test
public void testSslClientCertBadFormat() throws Exception {
String[] linesDeleted = Arrays.copyOf(CERTIFICATE_LINES, CERTIFICATE_LINES.length / 2);
String deleted = certificateSingleLine(linesDeleted, " ");
mockRequest.setHeader(valve.getSslClientCertHeader(), deleted);
TesterLogValidationFilter f = TesterLogValidationFilter.add(Level.WARNING, null,
"java.security.cert.CertificateException", "org.apache.catalina.valves.SSLValve");
valve.invoke(mockRequest, null);
EasyMock.verify(mockNext);
Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR));
Assert.assertEquals(1, f.getMessageCount());
}
@Test
public void testClientCertProviderNotFound() throws Exception {
EasyMock.expect(mockRequest.getConnector().getProperty("clientCertProvider")).andStubReturn("wontBeFound");
EasyMock.replay(mockRequest.getConnector());
mockRequest.setHeader(valve.getSslClientCertHeader(), certificateSingleLine(" "));
TesterLogValidationFilter f = TesterLogValidationFilter.add(Level.SEVERE, null,
"java.security.NoSuchProviderException", "org.apache.catalina.valves.SSLValve");
valve.invoke(mockRequest, null);
Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR));
Assert.assertEquals(1, f.getMessageCount());
}
@Test
public void testSslCipherHeaderPresent() throws Exception {
String cipher = "ciphered-with";
mockRequest.setHeader(valve.getSslCipherHeader(), cipher);
valve.invoke(mockRequest, null);
Assert.assertEquals(cipher, mockRequest.getAttribute(Globals.CIPHER_SUITE_ATTR));
}
@Test
public void testSslSessionIdHeaderPresent() throws Exception {
String session = "ssl-session";
mockRequest.setHeader(valve.getSslSessionIdHeader(), session);
valve.invoke(mockRequest, null);
Assert.assertEquals(session, mockRequest.getAttribute(Globals.SSL_SESSION_ID_ATTR));
}
@Test
public void testSslCipherUserKeySizeHeaderPresent() throws Exception {
Integer keySize = Integer.valueOf(452);
mockRequest.setHeader(valve.getSslCipherUserKeySizeHeader(), String.valueOf(keySize));
valve.invoke(mockRequest, null);
Assert.assertEquals(keySize, mockRequest.getAttribute(Globals.KEY_SIZE_ATTR));
}
@Test(expected = NumberFormatException.class)
public void testSslCipherUserKeySizeHeaderBadFormat() throws Exception {
mockRequest.setHeader(valve.getSslCipherUserKeySizeHeader(), "not-an-integer");
try {
valve.invoke(mockRequest, null);
} catch (NumberFormatException e) {
Assert.assertNull(mockRequest.getAttribute(Globals.KEY_SIZE_ATTR));
throw e;
}
}
private static String certificateSingleLine(String[] lines, String separator) {
StringBuilder singleSpaced = new StringBuilder();
for (String current : lines) {
singleSpaced.append(current).append(separator);
}
singleSpaced.deleteCharAt(singleSpaced.length() - 1);
return singleSpaced.toString();
}
private static String certificateSingleLine(String separator) {
return certificateSingleLine(CERTIFICATE_LINES, separator);
}
private void assertCertificateParsed() throws Exception {
TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null,
"org.apache.catalina.valves.SSLValve");
EasyMock.verify(mockNext);
X509Certificate[] certificates = (X509Certificate[]) mockRequest.getAttribute(Globals.CERTIFICATES_ATTR);
Assert.assertNotNull(certificates);
Assert.assertEquals(1, certificates.length);
Assert.assertNotNull(certificates[0]);
Assert.assertEquals(0, f.getMessageCount());
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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.valves;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
public class TestStuckThreadDetectionValve extends TomcatBaseTest {
private StandardContext context;
private Tomcat tomcat;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
tomcat = getTomcatInstance();
File docBase = new File(System.getProperty("java.io.tmpdir"));
context = (StandardContext) tomcat.addContext("",
docBase.getAbsolutePath());
}
@Test
public void testDetection() throws Exception {
// second, we test the actual effect of the flag on the startup
StickingServlet stickingServlet = new StickingServlet(8000L);
Wrapper servlet = Tomcat.addServlet(context, "myservlet",
stickingServlet);
servlet.addMapping("/myservlet");
StuckThreadDetectionValve valve = new StuckThreadDetectionValve();
valve.setThreshold(2);
context.addValve(valve);
context.setBackgroundProcessorDelay(1);
tomcat.start();
Assert.assertEquals(0, valve.getStuckThreadIds().length);
final ByteChunk result = new ByteChunk();
Thread asyncThread = new Thread() {
@Override
public void run() {
try {
getUrl("http://localhost:" + getPort() + "/myservlet",
result, null);
} catch (IOException e) {
e.printStackTrace();
}
}
};
asyncThread.start();
try {
Thread.sleep(500L);
Assert.assertEquals(0, valve.getStuckThreadIds().length);
Thread.sleep(5000L);
Assert.assertEquals(1, valve.getStuckThreadIds().length);
} finally {
asyncThread.join(20000);
// check that we did not reach the join timeout
Assert.assertFalse(asyncThread.isAlive());
}
Assert.assertFalse(stickingServlet.wasInterrupted);
Assert.assertTrue(result.toString().startsWith("OK"));
}
@Test
public void testInterruption() throws Exception {
// second, we test the actual effect of the flag on the startup
StickingServlet stickingServlet = new StickingServlet(
TimeUnit.SECONDS.toMillis(20L));
Wrapper servlet = Tomcat.addServlet(context, "myservlet",
stickingServlet);
servlet.addMapping("/myservlet");
StuckThreadDetectionValve valve = new StuckThreadDetectionValve();
valve.setThreshold(2);
valve.setInterruptThreadThreshold(5);
context.addValve(valve);
context.setBackgroundProcessorDelay(1);
tomcat.start();
Assert.assertEquals(0, valve.getStuckThreadIds().length);
final ByteChunk result = new ByteChunk();
Thread asyncThread = new Thread() {
@Override
public void run() {
try {
getUrl("http://localhost:" + getPort() + "/myservlet",
result, null);
} catch (IOException e) {
e.printStackTrace();
}
}
};
asyncThread.start();
try {
Thread.sleep(4000L);
Assert.assertEquals(1, valve.getStuckThreadIds().length);
} finally {
asyncThread.join(20000);
// check that we did not reach the join timeout
Assert.assertFalse(asyncThread.isAlive());
}
Assert.assertTrue(stickingServlet.wasInterrupted);
Assert.assertEquals(0, valve.getStuckThreadIds().length);
Assert.assertTrue(result.toString().startsWith("OK"));
}
private static class StickingServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final long delay;
boolean wasInterrupted = false;
StickingServlet(long delay) {
this.delay = delay;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
wasInterrupted = true;
}
resp.setContentType("text/plain");
resp.getWriter().println("OK");
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.valves;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.ServletException;
import org.junit.Assert;
import org.apache.catalina.AccessLog;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class TesterAccessLogValve extends ValveBase implements AccessLog {
private static final boolean RELAX_TIMING = Boolean.getBoolean("tomcat.test.relaxTiming");
// Timing tests need an error margin to prevent failures.
private static final long ERROR_MARGIN = RELAX_TIMING ? 2000 : 100;
private final Queue<Entry> entries = new ConcurrentLinkedQueue<>();
public TesterAccessLogValve() {
// Async requests are supported
super(true);
}
@Override
public void log(Request request, Response response, long time) {
entries.add(new Entry(request.getRequestURI(), response.getStatus(),
time));
}
@Override
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
// NOOP - test code
}
@Override
public boolean getRequestAttributesEnabled() {
// Always false - test code
return false;
}
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
// Just invoke next - access logging happens via log() method
getNext().invoke(request, response);
}
public int getEntryCount() {
return entries.size();
}
public void validateAccessLog(int count, int status, long minTime,
long maxTime) throws Exception {
// Wait (but not too long) until all expected entries appear (access log
// entry will be made after response has been returned to user)
for (int i = 0; i < 10 && entries.size() < count; i++) {
Thread.sleep(100);
}
StringBuilder entriesLog = new StringBuilder();
for (Entry entry : entries) {
entriesLog.append(entry.toString());
entriesLog.append(System.lineSeparator());
}
Assert.assertEquals(entriesLog.toString(), count, entries.size());
for (Entry entry : entries) {
Assert.assertEquals(status, entry.getStatus());
Assert.assertTrue(entry.toString() + " duration is not >= " + (minTime - ERROR_MARGIN),
entry.getTime() >= minTime - ERROR_MARGIN);
Assert.assertTrue(entry.toString() + " duration is not < " + (maxTime + ERROR_MARGIN),
entry.getTime() < maxTime + ERROR_MARGIN);
}
}
public static class Entry {
private final String uri;
private final int status;
private final long time;
public Entry(String uri, int status, long time) {
this.uri = uri;
this.status = status;
this.time = time;
}
public String getUri() {
return uri;
}
public int getStatus() {
return status;
}
public long getTime() {
return time;
}
@Override
public String toString() {
return "Uri: " + uri + ", Status: " + status + ", Time: " + time;
}
}
}

View File

@@ -0,0 +1,704 @@
/*
* 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.valves.rewrite;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.servlets.DefaultServlet;
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;
/*
* Implementation note:
*
* A number of these tests involve the rewrite valve returning an HTTP Location
* header that include un-encoded UTF-8 bytes. How the HTTP client handles these
* depends on the default character encoding configured for the JVM running the
* test. The tests expect the client to be configured with UTF-8 as the default
* encoding. Use of any other encoding is likely to lead to test failures.
*/
public class TestRewriteValve extends TomcatBaseTest {
@Test
public void testNoRewrite() throws Exception {
doTestRewrite("", "/a/%255A", "/a/%255A");
}
@Test
public void testBackslashPercentSign() throws Exception {
doTestRewrite("RewriteRule ^(.*) /a/\\%5A", "/", "/a/%255A");
}
@Test
public void testNoopRewrite() throws Exception {
doTestRewrite("RewriteRule ^(.*) $1", "/a/%255A", "/a/%255A");
}
@Test
public void testPathRewrite() throws Exception {
doTestRewrite("RewriteRule ^/b(.*) /a$1", "/b/%255A", "/a/%255A");
}
@Test
public void testNonNormalizedPathRewrite() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /b/../a/$1", "/b/%255A", "/b/../a/%255A");
}
// BZ 57863
@Test
public void testRewriteMap01() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/(.*).html$ /c/${mapa:$1}", "/b/a.html", "/c/aa");
}
@Test
public void testRewriteMap02() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/(.*).html$ /c/${mapa:$1|dd}", "/b/x.html", "/c/dd");
}
// BZ 62667
@Test
public void testRewriteMap03() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/(.*).html$ /c/${mapa:$1|d$1d}", "/b/x.html", "/c/dxd");
}
@Test
public void testRewriteMap04() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/(.*).html$ /c/${mapa:a$1|dd}", "/b/a.html", "/c/aaaa");
}
@Test
public void testRewriteMap05() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/.* /c/${mapa:a}", "/b/a.html", "/c/aa");
}
@Test
public void testRewriteMap06() throws Exception {
doTestRewrite("RewriteMap mapa org.apache.catalina.valves.rewrite.TesterRewriteMapA\n" +
"RewriteRule /b/.* /c/${mapa:${mapa:a}}", "/b/a.html", "/c/aaaa");
}
@Test
public void testRewriteServerVar() throws Exception {
doTestRewrite("RewriteRule /b/(.*).html$ /c%{SERVLET_PATH}", "/b/x.html", "/c/b/x.html");
}
@Test
public void testRewriteEnvVarAndServerVar() throws Exception {
System.setProperty("some_variable", "something");
doTestRewrite("RewriteRule /b/(.*).html$ /c/%{ENV:some_variable}%{SERVLET_PATH}",
"/b/x.html", "/c/something/b/x.html");
}
@Test
public void testRewriteServerVarAndEnvVar() throws Exception {
System.setProperty("some_variable", "something");
doTestRewrite("RewriteRule /b/(.*).html$ /c%{SERVLET_PATH}/%{ENV:some_variable}",
"/b/x.html", "/c/b/x.html/something");
}
@Test
public void testRewriteMissingCurlyBraceOnVar() throws Exception {
try {
doTestRewrite("RewriteRule /b/(.*).html$ /c%_{SERVLET_PATH}", "/b/x.html", "/c");
Assert.fail("IAE expected.");
} catch (java.lang.IllegalArgumentException e) {
// expected as %_{ is invalid
}
}
@Test
public void testRewriteMissingCurlyBraceOnMapper() throws Exception {
try {
doTestRewrite("RewriteRule /b/(.*).html$ /c$_{SERVLET_PATH}", "/b/x.html", "/c");
Assert.fail("IAE expected.");
} catch (java.lang.IllegalArgumentException e) {
// expected as $_{ is invalid
}
}
// https://bz.apache.org/bugzilla/show_bug.cgi?id=60013
public void testRewriteWithEncoding02() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*)$ /c/?param=$1 [L]",
"/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95", "/c/", "param=\u5728\u7EBF\u6D4B\u8BD5");
}
@Test
public void testNonAsciiPath() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c/$1", "/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
"/c/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
}
@Test
public void testNonAsciiPathRedirect() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [R]",
"/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
"/c/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
}
@Test
public void testQueryString() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c?$1", "/b/id=1", "/c", "id=1");
}
@Test
public void testQueryStringRemove() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c/$1?", "/b/d?=1", "/c/d", null);
}
@Test
public void testQueryStringRemove02() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSD]", "/b/d?=1", "/c/d", null);
}
@Test
public void testNonAsciiQueryString() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c?$1",
"/b/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
"/c", "id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
}
@Test
public void testNonAsciiQueryStringAndPath() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/$1?$2",
"/b/%E5%9C%A8%E7%BA%BF/id=%E6%B5%8B%E8%AF%95",
"/c/%E5%9C%A8%E7%BA%BF", "id=%E6%B5%8B%E8%AF%95");
}
@Test
public void testNonAsciiQueryStringAndRedirect() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*) /c?$1 [R]",
"/b/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
"/c", "id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
}
@Test
public void testNonAsciiQueryStringAndPathAndRedirect() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/$1?$2 [R]",
"/b/%E5%9C%A8%E7%BA%BF/id=%E6%B5%8B%E8%AF%95",
"/c/%E5%9C%A8%E7%BA%BF", "id=%E6%B5%8B%E8%AF%95");
}
@Test
public void testNonAsciiQueryStringWithB() throws Exception {
doTestRewrite("RewriteRule ^/b/(.*)/id=(.*) /c?filename=$1&id=$2 [B]",
"/b/file01/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95", "/c",
"filename=file01&id=%25E5%259C%25A8%25E7%25BA%25BF%25E6%25B5%258B%25E8%25AF%2595");
}
@Test
public void testNonAsciiQueryStringAndPathAndRedirectWithB() throws Exception {
// Note the double encoding of the result (httpd produces the same result)
doTestRewrite("RewriteRule ^/b/(.*)/(.*)/id=(.*) /c/$1?filename=$2&id=$3 [B,R]",
"/b/%E5%9C%A8%E7%BA%BF/file01/id=%E6%B5%8B%E8%AF%95",
"/c/%25E5%259C%25A8%25E7%25BA%25BF",
"filename=file01&id=%25E6%25B5%258B%25E8%25AF%2595");
}
@Test
public void testUtf8WithBothQsFlagsNone() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithBothQsFlagsB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
}
@Test
public void testUtf8WithBothQsFlagsR() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithBothQsFlagsRB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
}
@Test
public void testUtf8WithBothQsFlagsRNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
}
@Test
public void testUtf8WithBothQsFlagsRBNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
}
@Test
public void testUtf8WithBothQsFlagsBQSA() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B,QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1",
"id=%25C2%25A1&di=%C2%AE");
}
@Test
public void testUtf8WithBothQsFlagsRQSA() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1",
"id=%C2%A1&di=%C2%AE");
}
@Test
public void testUtf8WithBothQsFlagsRBQSA() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1",
"id=%25C2%25A1&di=%C2%AE");
}
@Test
public void testUtf8WithBothQsFlagsRNEQSA() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE,QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
}
@Test
public void testUtf8WithBothQsFlagsRBNEQSA() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE,QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
}
@Test
public void testUtf8WithOriginalQsFlagsNone() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1",
"/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithOriginalQsFlagsB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]",
"/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%C2%A1");
}
@Test
public void testUtf8WithOriginalQsFlagsR() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R]",
"/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithOriginalQsFlagsRB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]",
"/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%C2%A1");
}
@Test
public void testUtf8WithOriginalQsFlagsRNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]",
"/b/%C2%A1?id=%C2%A1", null);
}
@Test
public void testUtf8WithOriginalQsFlagsRBNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]",
"/b/%C2%A1?id=%C2%A1", null);
}
@Test
public void testUtf8WithRewriteQsFlagsNone() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2",
"/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithRewriteQsFlagsB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]",
"/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
}
@Test
public void testUtf8WithRewriteQsFlagsR() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R]",
"/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
}
@Test
public void testUtf8WithBothQsFlagsQSA() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [QSA]",
"/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1",
"id=%C2%A1&di=%C2%AE");
}
@Test
public void testUtf8WithRewriteQsFlagsRB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]",
"/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
}
@Test
public void testUtf8WithRewriteQsFlagsRNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
"/b/%C2%A1/id=%C2%A1", null);
}
@Test
public void testUtf8WithRewriteQsFlagsRBNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]",
"/b/%C2%A1/id=%C2%A1", null);
}
@Test
public void testUtf8WithRewriteQsFlagsQSA() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [QSA]",
"/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1",
"id=%C2%A1");
}
@Test
public void testUtf8FlagsNone() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1", "/b/%C2%A1", "/c/%C2%A1%C2%A1");
}
@Test
public void testUtf8FlagsB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1", "/c/%C2%A1%25C2%25A1");
}
@Test
public void testUtf8FlagsR() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R]", "/b/%C2%A1", "/c/%C2%A1%C2%A1");
}
@Test
public void testUtf8FlagsRB() throws Exception {
// Note %C2%A1 == \u00A1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1", "/c/%C2%A1%25C2%25A1");
}
@Test
public void testUtf8FlagsRNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", "/b/%C2%A1", null);
}
@Test
public void testUtf8FlagsRBNE() throws Exception {
// Note %C2%A1 == \u00A1
// Failing to escape the redirect means UTF-8 bytes in the Location
// header which will be treated as if they are ISO-8859-1
doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", "/b/%C2%A1", null);
}
@Test
public void testFlagsNC() throws Exception {
// https://bz.apache.org/bugzilla/show_bug.cgi?id=60116
doTestRewrite("RewriteCond %{QUERY_STRING} a=([a-z]*) [NC]\n"
+ "RewriteRule .* - [E=X-Test:%1]",
"/c?a=aAa", "/c", null, "aAa");
}
@Test
public void testHostRewrite() throws Exception {
// Based on report from users list that ':' was encoded and breaking
// the redirect
doTestRewrite("RewriteRule ^/b(.*) http://%{HTTP_HOST}:%{SERVER_PORT}/a$1 [R]",
"/b/%255A", "/a/%255A");
}
@Test
public void testDefaultRedirect() throws Exception {
doTestRedirect("RewriteRule ^/from/a$ /to/b [R]", "/redirect/from/a", "/redirect/to/b",
302);
}
@Test
public void testTempRedirect() throws Exception {
doTestRedirect("RewriteRule ^/from/a$ /to/b [R=temp]", "/redirect/from/a", "/redirect/to/b",
302);
}
@Test
public void testPermanentRedirect() throws Exception {
// Disable the following of redirects for this test only
boolean originalValue = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
try {
doTestRedirect("RewriteRule ^/from/a$ /to/b [R=permanent]", "/redirect/from/a", "/redirect/to/b",
301);
} finally {
HttpURLConnection.setFollowRedirects(originalValue);
}
}
@Test
public void testSeeotherRedirect() throws Exception {
// Disable the following of redirects for this test only
boolean originalValue = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
try {
doTestRedirect("RewriteRule ^/from/a$ /to/b [R=seeother]", "/redirect/from/a", "/redirect/to/b",
303);
} finally {
HttpURLConnection.setFollowRedirects(originalValue);
}
}
@Test
public void test307Redirect() throws Exception {
// Disable the following of redirects for this test only
boolean originalValue = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
try {
doTestRedirect("RewriteRule ^/from/a$ /to/b [R=307]", "/redirect/from/a", "/redirect/to/b",
307);
} finally {
HttpURLConnection.setFollowRedirects(originalValue);
}
}
@Test
public void testBackReferenceRewrite() throws Exception {
doTestRewrite("RewriteRule ^/b/(rest)?$ /c/$1", "/b/rest", "/c/rest");
}
@Test
public void testEmptyBackReferenceRewrite() throws Exception {
doTestRewrite("RewriteRule ^/b/(rest)?$ /c/$1", "/b/", "/c/");
}
@Test
public void testNegativePattern01() throws Exception {
doTestRewrite("RewriteRule !^/b/.* /c/", "/b", "/c/");
}
@Test
public void testNegativePattern02() throws Exception {
doTestRewrite("RewriteRule !^/b/.* /c/", "/d/e/f", "/c/");
}
@Test
public void testNegativePattern03() throws Exception {
doTestRewrite("RewriteRule !^/c/.* /b/", "/c/", "/c/");
}
@Test
public void testNegativePattern04() throws Exception {
doTestRewrite("RewriteRule !^/c/.* /b/", "/c/d", "/c/d");
}
private void doTestRewrite(String config, String request, String expectedURI) throws Exception {
doTestRewrite(config, request, expectedURI, null);
}
private void doTestRewrite(String config, String request, String expectedURI,
String expectedQueryString) throws Exception {
doTestRewrite(config, request, expectedURI, expectedQueryString, null);
}
private void doTestRewrite(String config, String request, String expectedURI,
String expectedQueryString, String expectedAttributeValue) throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("", null);
RewriteValve rewriteValve = new RewriteValve();
ctx.getPipeline().addValve(rewriteValve);
rewriteValve.setConfiguration(config);
Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
ctx.addServletMappingDecoded("/a/%5A", "snoop");
ctx.addServletMappingDecoded("/c/*", "snoop");
Tomcat.addServlet(ctx, "default", new DefaultServlet());
ctx.addServletMappingDecoded("/", "default");
tomcat.start();
ByteChunk res = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort() + request, res, null);
res.setCharset(StandardCharsets.UTF_8);
if (expectedURI == null) {
// Rewrite is expected to fail. Probably because invalid characters
// were written into the request target
Assert.assertEquals(400, rc);
} else {
String body = res.toString();
RequestDescriptor requestDesc = SnoopResult.parse(body);
String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
Assert.assertEquals(expectedURI, requestURI);
if (expectedQueryString != null) {
String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING");
Assert.assertEquals(expectedQueryString, queryString);
}
if (expectedAttributeValue != null) {
String attributeValue = requestDesc.getAttribute("X-Test");
Assert.assertEquals(expectedAttributeValue, attributeValue);
}
}
}
private void doTestRedirect(String config, String request, String expectedURI,
int expectedStatusCode) throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
Context ctx = tomcat.addContext("redirect", null);
RewriteValve rewriteValve = new RewriteValve();
ctx.getPipeline().addValve(rewriteValve);
rewriteValve.setConfiguration(config);
Tomcat.addServlet(ctx, "tester", new TesterServlet());
ctx.addServletMappingDecoded("/from/a", "tester");
ctx.addServletMappingDecoded("/to/b", "tester");
tomcat.start();
ByteChunk res = new ByteChunk();
Map<String, List<String>> resHead = new HashMap<>();
int rc = methodUrl("http://localhost:" + getPort() + request, res,
DEFAULT_CLIENT_TIMEOUT_MS, null, resHead, "GET", false);
res.setCharset(StandardCharsets.UTF_8);
if (expectedURI == null) {
// Rewrite is expected to fail. Probably because invalid characters
// were written into the request target
Assert.assertEquals(400, rc);
} else {
List<String> locations = resHead.get("Location");
Assert.assertFalse(locations.isEmpty());
String redirectURI = locations.get(0);
Assert.assertEquals(expectedURI, redirectURI);
Assert.assertEquals(expectedStatusCode, rc);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.valves.rewrite;
import java.util.HashMap;
import java.util.Map;
public class TesterRewriteMapA implements RewriteMap {
private static final Map<String,String> map = new HashMap<>();
static {
map.put("a", "aa");
map.put("aa", "aaaa");
map.put("b", "bb");
}
@Override
public String setParameters(String params) {
// NO-OP
return null;
}
@Override
public String lookup(String key) {
return map.get(key);
}
}