init
This commit is contained in:
508
test/org/apache/catalina/valves/Benchmarks.java
Normal file
508
test/org/apache/catalina/valves/Benchmarks.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
test/org/apache/catalina/valves/TestAccessLogValve.java
Normal file
94
test/org/apache/catalina/valves/TestAccessLogValve.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
214
test/org/apache/catalina/valves/TestErrorReportValve.java
Normal file
214
test/org/apache/catalina/valves/TestErrorReportValve.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("\""));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
1148
test/org/apache/catalina/valves/TestRemoteIpValve.java
Normal file
1148
test/org/apache/catalina/valves/TestRemoteIpValve.java
Normal file
File diff suppressed because it is too large
Load Diff
346
test/org/apache/catalina/valves/TestRequestFilterValve.java
Normal file
346
test/org/apache/catalina/valves/TestRequestFilterValve.java
Normal 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");
|
||||
}
|
||||
}
|
||||
333
test/org/apache/catalina/valves/TestSSLValve.java
Normal file
333
test/org/apache/catalina/valves/TestSSLValve.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
125
test/org/apache/catalina/valves/TesterAccessLogValve.java
Normal file
125
test/org/apache/catalina/valves/TesterAccessLogValve.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
704
test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
Normal file
704
test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user