init
This commit is contained in:
117
test/org/apache/catalina/ant/TestDeployTask.java
Normal file
117
test/org/apache/catalina/ant/TestDeployTask.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.ant;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
|
||||
public class TestDeployTask extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void bug58086a() {
|
||||
DeployTask deployTask = new DeployTask() {
|
||||
|
||||
@Override
|
||||
public void execute(String command, InputStream istream, String contentType, long contentLength)
|
||||
throws BuildException {
|
||||
Assert.assertEquals("/deploy?path=somepath", command);
|
||||
Assert.assertEquals("application/octet-stream", contentType);
|
||||
try {
|
||||
istream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
setDefaults(deployTask);
|
||||
|
||||
testExecute(deployTask, "file:./test/deployment/context.war");
|
||||
testExecute(deployTask, new File("test/deployment/context.war").toURI().toString());
|
||||
testExecute(deployTask, new File("test/deployment/context.war").getAbsolutePath());
|
||||
testExecute(deployTask, "jar:" + new File("test/deployment/context.jar").toURI().toString() + "!/context.war");
|
||||
testExecute(deployTask, "file:./test/deployment/dir with spaces/context.war");
|
||||
testExecute(deployTask, new File("test/deployment/dir with spaces/context.war").toURI().toString());
|
||||
testExecute(deployTask, new File("test/deployment/dir with spaces/context.war").getAbsolutePath());
|
||||
testExecute(deployTask, "jar:" + new File("test/deployment/dir with spaces/context.jar").toURI().toString()
|
||||
+ "!/context.war");
|
||||
testExecute(deployTask, "file:./test/deployment/dir%20with%20spaces/context.war");
|
||||
}
|
||||
|
||||
@Test(expected = BuildException.class)
|
||||
public void bug58086b() {
|
||||
DeployTask deployTask = new DeployTask();
|
||||
setDefaults(deployTask);
|
||||
testExecute(deployTask, "scheme:./test/deployment/context.war");
|
||||
}
|
||||
|
||||
@Test(expected = BuildException.class)
|
||||
public void bug58086c() {
|
||||
DeployTask deployTask = new DeployTask();
|
||||
setDefaults(deployTask);
|
||||
testExecute(deployTask, "sc:./test/deployment/context.war");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bug58086d() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File root = new File("test/deployment");
|
||||
tomcat.addWebapp("", root.getAbsolutePath());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
DeployTask deployTask = new DeployTask() {
|
||||
|
||||
@Override
|
||||
public void execute(String command, InputStream istream, String contentType, long contentLength)
|
||||
throws BuildException {
|
||||
Assert.assertEquals("/deploy?path=somepath", command);
|
||||
Assert.assertEquals("application/octet-stream", contentType);
|
||||
try {
|
||||
istream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
setDefaults(deployTask);
|
||||
|
||||
testExecute(deployTask, "http://localhost:" + getPort() + "/context.war");
|
||||
}
|
||||
|
||||
private void setDefaults(DeployTask deployTask) {
|
||||
deployTask.setUrl("someurl");
|
||||
deployTask.setUsername("someuser");
|
||||
deployTask.setPassword("somepassword");
|
||||
deployTask.setPath("somepath");
|
||||
}
|
||||
|
||||
private void testExecute(DeployTask deployTask, String war) {
|
||||
deployTask.setWar(war);
|
||||
deployTask.execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.authenticator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class incorporates test response data
|
||||
*/
|
||||
class ResponseDescriptor {
|
||||
private Map<String, List<String>> headers;
|
||||
private String body;
|
||||
private int responseCode;
|
||||
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
public void setHeaders(Map<String, List<String>> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
|
||||
public int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
|
||||
public void setResponseCode(int responseCode) {
|
||||
this.responseCode = responseCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.authenticator;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.TesterMapRealm;
|
||||
import org.apache.catalina.startup.TesterServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.catalina.valves.RemoteIpValve;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
|
||||
public class TestAuthInfoResponseHeaders extends TomcatBaseTest {
|
||||
|
||||
private static String USER = "user";
|
||||
private static String PWD = "pwd";
|
||||
private static String ROLE = "role";
|
||||
private static String URI = "/protected";
|
||||
private static String CONTEXT_PATH = "/foo";
|
||||
private static String CLIENT_AUTH_HEADER = "authorization";
|
||||
|
||||
/*
|
||||
* Encapsulate the logic to generate an HTTP header
|
||||
* for BASIC Authentication.
|
||||
* Note: only used internally, so no need to validate arguments.
|
||||
*/
|
||||
private static final class BasicCredentials {
|
||||
|
||||
private final String method;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String credentials;
|
||||
|
||||
private BasicCredentials(String aMethod,
|
||||
String aUsername, String aPassword) {
|
||||
method = aMethod;
|
||||
username = aUsername;
|
||||
password = aPassword;
|
||||
String userCredentials = username + ":" + password;
|
||||
byte[] credentialsBytes =
|
||||
userCredentials.getBytes(StandardCharsets.ISO_8859_1);
|
||||
String base64auth = Base64.encodeBase64String(credentialsBytes);
|
||||
credentials= method + " " + base64auth;
|
||||
}
|
||||
|
||||
private String getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHeaders() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithHeaders() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, true);
|
||||
}
|
||||
|
||||
public void doTest(String user, String pwd, String uri, boolean expectResponseAuthHeaders)
|
||||
throws Exception {
|
||||
|
||||
if (expectResponseAuthHeaders) {
|
||||
BasicAuthenticator auth =
|
||||
(BasicAuthenticator) getTomcatInstance().getHost().findChild(
|
||||
CONTEXT_PATH).getPipeline().getFirst();
|
||||
auth.setSendAuthInfoResponseHeaders(true);
|
||||
}
|
||||
getTomcatInstance().start();
|
||||
|
||||
Map<String,List<String>> reqHeaders = new HashMap<>();
|
||||
|
||||
List<String> auth = new ArrayList<>();
|
||||
auth.add(new BasicCredentials("Basic", user, pwd).getCredentials());
|
||||
reqHeaders.put(CLIENT_AUTH_HEADER, auth);
|
||||
|
||||
List<String> forwardedFor = new ArrayList<>();
|
||||
forwardedFor.add("192.168.0.10");
|
||||
List<String> forwardedHost = new ArrayList<>();
|
||||
forwardedHost.add("localhost");
|
||||
reqHeaders.put("X-Forwarded-For", forwardedFor);
|
||||
reqHeaders.put("X-Forwarded-Host", forwardedHost);
|
||||
|
||||
Map<String,List<String>> respHeaders = new HashMap<>();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + uri, bc, reqHeaders,
|
||||
respHeaders);
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
|
||||
if (expectResponseAuthHeaders) {
|
||||
List<String> remoteUsers = respHeaders.get("remote-user");
|
||||
Assert.assertNotNull(remoteUsers);
|
||||
Assert.assertEquals(USER, remoteUsers.get(0));
|
||||
List<String> authTypes = respHeaders.get("auth-type");
|
||||
Assert.assertNotNull(authTypes);
|
||||
Assert.assertEquals(HttpServletRequest.BASIC_AUTH, authTypes.get(0));
|
||||
} else {
|
||||
Assert.assertFalse(respHeaders.containsKey("remote-user"));
|
||||
Assert.assertFalse(respHeaders.containsKey("auth-type"));
|
||||
}
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Configure a context with digest auth and a single protected resource
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
tomcat.getHost().getPipeline().addValve(new RemoteIpValve());
|
||||
|
||||
// No file system docBase required
|
||||
Context ctxt = tomcat.addContext(CONTEXT_PATH, null);
|
||||
|
||||
// Add protected servlet
|
||||
Tomcat.addServlet(ctxt, "TesterServlet", new TesterServlet());
|
||||
ctxt.addServletMappingDecoded(URI, "TesterServlet");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPatternDecoded(URI);
|
||||
SecurityConstraint sc = new SecurityConstraint();
|
||||
sc.addAuthRole(ROLE);
|
||||
sc.addCollection(collection);
|
||||
ctxt.addConstraint(sc);
|
||||
|
||||
// Configure the Realm
|
||||
TesterMapRealm realm = new TesterMapRealm();
|
||||
realm.addUser(USER, PWD);
|
||||
realm.addUserRole(USER, ROLE);
|
||||
ctxt.setRealm(realm);
|
||||
|
||||
// Configure the authenticator
|
||||
LoginConfig lc = new LoginConfig();
|
||||
lc.setAuthMethod(HttpServletRequest.BASIC_AUTH);
|
||||
ctxt.setLoginConfig(lc);
|
||||
ctxt.getPipeline().addValve(new BasicAuthenticator());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.authenticator;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Realm;
|
||||
import org.apache.catalina.authenticator.AuthenticatorBase.AllowCorsPreflight;
|
||||
import org.apache.catalina.filters.AddDefaultCharsetFilter;
|
||||
import org.apache.catalina.filters.CorsFilter;
|
||||
import org.apache.catalina.realm.NullRealm;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.FilterDef;
|
||||
import org.apache.tomcat.util.descriptor.web.FilterMap;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestAuthenticatorBaseCorsPreflight extends TomcatBaseTest {
|
||||
|
||||
private static final String ALLOWED_ORIGIN = "http://example.com";
|
||||
private static final String EMPTY_ORIGIN = "";
|
||||
private static final String INVALID_ORIGIN = "http://%20";
|
||||
private static final String SAME_ORIGIN = "http://localhost";
|
||||
private static final String ALLOWED_METHOD = "GET";
|
||||
private static final String BLOCKED_METHOD = "POST";
|
||||
private static final String EMPTY_METHOD = "";
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: input[{0}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.NEVER, "/*", "OPTIONS", null, null, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", null, null, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.TRUE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", EMPTY_ORIGIN, ALLOWED_METHOD, Boolean.FALSE});
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", INVALID_ORIGIN, ALLOWED_METHOD, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", SAME_ORIGIN, ALLOWED_METHOD, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "GET", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, BLOCKED_METHOD, Boolean.FALSE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, EMPTY_METHOD, Boolean.FALSE});
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, null, Boolean.FALSE});
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.FILTER, "/*", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.TRUE });
|
||||
parameterSets.add(new Object[] { AllowCorsPreflight.FILTER, "/x", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.FALSE });
|
||||
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public AllowCorsPreflight allowCorsPreflight;
|
||||
@Parameter(1)
|
||||
public String filterMapping;
|
||||
@Parameter(2)
|
||||
public String method;
|
||||
@Parameter(3)
|
||||
public String origin;
|
||||
@Parameter(4)
|
||||
public String accessControl;
|
||||
@Parameter(5)
|
||||
public boolean allow;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
// So the test can set the origin header
|
||||
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context ctx = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
|
||||
Tomcat.addServlet(ctx, "default", new DefaultServlet());
|
||||
ctx.addServletMappingDecoded("/", "default");
|
||||
|
||||
LoginConfig loginConfig = new LoginConfig();
|
||||
loginConfig.setAuthMethod("BASIC");
|
||||
ctx.setLoginConfig(loginConfig);
|
||||
|
||||
BasicAuthenticator basicAuth = new BasicAuthenticator();
|
||||
basicAuth.setAllowCorsPreflight(allowCorsPreflight.toString());
|
||||
ctx.getPipeline().addValve(basicAuth);
|
||||
|
||||
Realm realm = new NullRealm();
|
||||
ctx.setRealm(realm);
|
||||
|
||||
SecurityCollection securityCollection = new SecurityCollection();
|
||||
securityCollection.addPattern("/*");
|
||||
SecurityConstraint constraint = new SecurityConstraint();
|
||||
constraint.setAuthConstraint(true);
|
||||
constraint.addCollection(securityCollection);
|
||||
ctx.addConstraint(constraint);
|
||||
|
||||
// For code coverage
|
||||
FilterDef otherFilter = new FilterDef();
|
||||
otherFilter.setFilterName("other");
|
||||
otherFilter.setFilterClass(AddDefaultCharsetFilter.class.getName());
|
||||
FilterMap otherMap = new FilterMap();
|
||||
otherMap.setFilterName("other");
|
||||
otherMap.addURLPatternDecoded("/other");
|
||||
ctx.addFilterDef(otherFilter);
|
||||
ctx.addFilterMap(otherMap);
|
||||
|
||||
FilterDef corsFilter = new FilterDef();
|
||||
corsFilter.setFilterName("cors");
|
||||
corsFilter.setFilterClass(CorsFilter.class.getName());
|
||||
corsFilter.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, ALLOWED_ORIGIN);
|
||||
corsFilter.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_METHODS, ALLOWED_METHOD);
|
||||
FilterMap corsFilterMap = new FilterMap();
|
||||
corsFilterMap.setFilterName("cors");
|
||||
corsFilterMap.addURLPatternDecoded(filterMapping);
|
||||
ctx.addFilterDef(corsFilter);
|
||||
ctx.addFilterMap(corsFilterMap);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Map<String,List<String>> reqHead = new HashMap<>();
|
||||
if (origin != null) {
|
||||
List<String> values = new ArrayList<>();
|
||||
if (SAME_ORIGIN.equals(origin)) {
|
||||
values.add(origin + ":" + getPort());
|
||||
} else {
|
||||
values.add(origin);
|
||||
}
|
||||
reqHead.put(CorsFilter.REQUEST_HEADER_ORIGIN, values);
|
||||
}
|
||||
if (accessControl != null) {
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(accessControl);
|
||||
reqHead.put(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, values);
|
||||
}
|
||||
|
||||
ByteChunk out = new ByteChunk();
|
||||
int rc = methodUrl("http://localhost:" + getPort() + "/target", out, 300000, reqHead, null, method, false);
|
||||
|
||||
if (allow) {
|
||||
Assert.assertEquals(200, rc);
|
||||
} else {
|
||||
Assert.assertEquals(403, rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
558
test/org/apache/catalina/authenticator/TestBasicAuthParser.java
Normal file
558
test/org/apache/catalina/authenticator/TestBasicAuthParser.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 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.authenticator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.startup.TesterMapRealm;
|
||||
import org.apache.catalina.startup.TesterServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterContext;
|
||||
import org.apache.tomcat.unittest.TesterServletContext;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
|
||||
import org.apache.tomcat.util.security.MD5Encoder;
|
||||
|
||||
public class TestDigestAuthenticator extends TomcatBaseTest {
|
||||
|
||||
private static String USER = "user";
|
||||
private static String PWD = "pwd";
|
||||
private static String ROLE = "role";
|
||||
private static String URI = "/protected";
|
||||
private static String QUERY = "?foo=bar";
|
||||
private static String CONTEXT_PATH = "/foo";
|
||||
private static String CLIENT_AUTH_HEADER = "authorization";
|
||||
private static String REALM = "TestRealm";
|
||||
private static String CNONCE = "cnonce";
|
||||
private static String NC1 = "00000001";
|
||||
private static String NC2 = "00000002";
|
||||
private static String QOP = "auth";
|
||||
|
||||
|
||||
@Test
|
||||
public void bug54521() throws LifecycleException {
|
||||
DigestAuthenticator digestAuthenticator = new DigestAuthenticator();
|
||||
TesterContext context = new TesterContext();
|
||||
context.setServletContext(new TesterServletContext());
|
||||
digestAuthenticator.setContainer(context);
|
||||
digestAuthenticator.start();
|
||||
Request request = new TesterRequest();
|
||||
final int count = 1000;
|
||||
|
||||
Set<String> nonces = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
nonces.add(digestAuthenticator.generateNonce(request));
|
||||
}
|
||||
|
||||
Assert.assertEquals(count, nonces.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAllValid() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC2, CNONCE, QOP, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidNoQop() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
null, null, null, null, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidQuery() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI + QUERY, false, true, REALM, true,
|
||||
true, NC1, NC2, CNONCE, QOP, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidUriFail() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, true, true, REALM, true, true,
|
||||
NC1, NC2, CNONCE, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidUriPass() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, true, false, REALM, true, true,
|
||||
NC1, NC2, CNONCE, QOP, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidRealm() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, "null", true, true,
|
||||
NC1, NC2, CNONCE, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidNonce() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, false, true,
|
||||
NC1, NC2, CNONCE, QOP, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidOpaque() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, false,
|
||||
NC1, NC2, CNONCE, QOP, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidNc1() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
"null", null, CNONCE, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQop() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC2, CNONCE, "null", false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo1() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC2, CNONCE, null, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo2() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC2, null, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo3() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC2, null, null, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo4() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
null, null, CNONCE, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo5() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
null, null, CNONCE, null, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQopCombo6() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
null, null, null, QOP, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplay() throws Exception {
|
||||
doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
|
||||
NC1, NC1, CNONCE, QOP, true, false);
|
||||
}
|
||||
|
||||
public void doTest(String user, String pwd, String uri, boolean breakUri,
|
||||
boolean validateUri, String realm, boolean useServerNonce,
|
||||
boolean useServerOpaque, String nc1, String nc2, String cnonce,
|
||||
String qop, boolean req2expect200, boolean req3expect200)
|
||||
throws Exception {
|
||||
|
||||
if (!validateUri) {
|
||||
DigestAuthenticator auth =
|
||||
(DigestAuthenticator) getTomcatInstance().getHost().findChild(
|
||||
CONTEXT_PATH).getPipeline().getFirst();
|
||||
auth.setValidateUri(false);
|
||||
}
|
||||
getTomcatInstance().start();
|
||||
|
||||
String digestUri;
|
||||
if (breakUri) {
|
||||
digestUri = "/broken" + uri;
|
||||
} else {
|
||||
digestUri = uri;
|
||||
}
|
||||
List<String> auth = new ArrayList<>();
|
||||
auth.add(buildDigestResponse(user, pwd, digestUri, realm, "null",
|
||||
"null", nc1, cnonce, qop));
|
||||
Map<String,List<String>> reqHeaders = new HashMap<>();
|
||||
reqHeaders.put(CLIENT_AUTH_HEADER, auth);
|
||||
|
||||
Map<String,List<String>> respHeaders = new HashMap<>();
|
||||
|
||||
// The first request will fail - but we need to extract the nonce
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + uri, bc, reqHeaders,
|
||||
respHeaders);
|
||||
Assert.assertEquals(401, rc);
|
||||
Assert.assertTrue(bc.getLength() > 0);
|
||||
bc.recycle();
|
||||
|
||||
// Second request should succeed (if we use the server nonce)
|
||||
auth.clear();
|
||||
if (useServerNonce) {
|
||||
if (useServerOpaque) {
|
||||
auth.add(buildDigestResponse(user, pwd, digestUri, realm,
|
||||
getNonce(respHeaders), getOpaque(respHeaders), nc1,
|
||||
cnonce, qop));
|
||||
} else {
|
||||
auth.add(buildDigestResponse(user, pwd, digestUri, realm,
|
||||
getNonce(respHeaders), "null", nc1, cnonce, qop));
|
||||
}
|
||||
} else {
|
||||
auth.add(buildDigestResponse(user, pwd, digestUri, realm,
|
||||
"null", getOpaque(respHeaders), nc1, cnonce, QOP));
|
||||
}
|
||||
rc = getUrl("http://localhost:" + getPort() + uri, bc, reqHeaders,
|
||||
null);
|
||||
|
||||
if (req2expect200) {
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
} else {
|
||||
Assert.assertEquals(401, rc);
|
||||
Assert.assertTrue(bc.getLength() > 0);
|
||||
}
|
||||
|
||||
// Third request should succeed if we increment nc
|
||||
auth.clear();
|
||||
bc.recycle();
|
||||
auth.add(buildDigestResponse(user, pwd, digestUri, realm,
|
||||
getNonce(respHeaders), getOpaque(respHeaders), nc2, cnonce,
|
||||
qop));
|
||||
rc = getUrl("http://localhost:" + getPort() + uri, bc, reqHeaders,
|
||||
null);
|
||||
|
||||
if (req3expect200) {
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
} else {
|
||||
Assert.assertEquals(401, rc);
|
||||
Assert.assertTrue(bc.getLength() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Configure a context with digest auth and a single protected resource
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctxt = tomcat.addContext(CONTEXT_PATH, null);
|
||||
|
||||
// Add protected servlet
|
||||
Tomcat.addServlet(ctxt, "TesterServlet", new TesterServlet());
|
||||
ctxt.addServletMappingDecoded(URI, "TesterServlet");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPatternDecoded(URI);
|
||||
SecurityConstraint sc = new SecurityConstraint();
|
||||
sc.addAuthRole(ROLE);
|
||||
sc.addCollection(collection);
|
||||
ctxt.addConstraint(sc);
|
||||
|
||||
// Configure the Realm
|
||||
TesterMapRealm realm = new TesterMapRealm();
|
||||
realm.addUser(USER, PWD);
|
||||
realm.addUserRole(USER, ROLE);
|
||||
ctxt.setRealm(realm);
|
||||
|
||||
// Configure the authenticator
|
||||
LoginConfig lc = new LoginConfig();
|
||||
lc.setAuthMethod("DIGEST");
|
||||
lc.setRealmName(REALM);
|
||||
ctxt.setLoginConfig(lc);
|
||||
ctxt.getPipeline().addValve(new DigestAuthenticator());
|
||||
}
|
||||
|
||||
protected static String getNonce(Map<String,List<String>> respHeaders) {
|
||||
List<String> authHeaders =
|
||||
respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME);
|
||||
// Assume there is only one
|
||||
String authHeader = authHeaders.iterator().next();
|
||||
|
||||
int start = authHeader.indexOf("nonce=\"") + 7;
|
||||
int end = authHeader.indexOf('\"', start);
|
||||
return authHeader.substring(start, end);
|
||||
}
|
||||
|
||||
protected static String getOpaque(Map<String,List<String>> respHeaders) {
|
||||
List<String> authHeaders =
|
||||
respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME);
|
||||
// Assume there is only one
|
||||
String authHeader = authHeaders.iterator().next();
|
||||
|
||||
int start = authHeader.indexOf("opaque=\"") + 8;
|
||||
int end = authHeader.indexOf('\"', start);
|
||||
return authHeader.substring(start, end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Notes from RFC2617
|
||||
* H(data) = MD5(data)
|
||||
* KD(secret, data) = H(concat(secret, ":", data))
|
||||
* A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
||||
* A2 = Method ":" digest-uri-value
|
||||
* request-digest = <"> < KD ( H(A1), unq(nonce-value)
|
||||
":" nc-value
|
||||
":" unq(cnonce-value)
|
||||
":" unq(qop-value)
|
||||
":" H(A2)
|
||||
) <">
|
||||
*/
|
||||
private static String buildDigestResponse(String user, String pwd,
|
||||
String uri, String realm, String nonce, String opaque, String nc,
|
||||
String cnonce, String qop) {
|
||||
|
||||
String a1 = user + ":" + realm + ":" + pwd;
|
||||
String a2 = "GET:" + uri;
|
||||
|
||||
String md5a1 = digest(a1);
|
||||
String md5a2 = digest(a2);
|
||||
|
||||
String response;
|
||||
if (qop == null) {
|
||||
response = md5a1 + ":" + nonce + ":" + md5a2;
|
||||
} else {
|
||||
response = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" +
|
||||
qop + ":" + md5a2;
|
||||
}
|
||||
|
||||
String md5response = digest(response);
|
||||
|
||||
StringBuilder auth = new StringBuilder();
|
||||
auth.append("Digest username=\"");
|
||||
auth.append(user);
|
||||
auth.append("\", realm=\"");
|
||||
auth.append(realm);
|
||||
auth.append("\", nonce=\"");
|
||||
auth.append(nonce);
|
||||
auth.append("\", uri=\"");
|
||||
auth.append(uri);
|
||||
auth.append("\", opaque=\"");
|
||||
auth.append(opaque);
|
||||
auth.append("\", response=\"");
|
||||
auth.append(md5response);
|
||||
auth.append("\"");
|
||||
if (qop != null) {
|
||||
auth.append(", qop=");
|
||||
auth.append(qop);
|
||||
auth.append("");
|
||||
}
|
||||
if (nc != null) {
|
||||
auth.append(", nc=");
|
||||
auth.append(nc);
|
||||
}
|
||||
if (cnonce != null) {
|
||||
auth.append(", cnonce=\"");
|
||||
auth.append(cnonce);
|
||||
auth.append("\"");
|
||||
}
|
||||
|
||||
return auth.toString();
|
||||
}
|
||||
|
||||
private static String digest(String input) {
|
||||
return MD5Encoder.encode(
|
||||
ConcurrentMessageDigest.digestMD5(input.getBytes()));
|
||||
}
|
||||
|
||||
|
||||
private static class TesterRequest extends Request {
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.authenticator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl;
|
||||
import org.apache.catalina.connector.Request;
|
||||
|
||||
public class TestJaspicCallbackHandlerInAuthenticator {
|
||||
|
||||
@Test
|
||||
public void testCustomCallbackHandlerCreation() throws Exception {
|
||||
testCallbackHandlerCreation("org.apache.catalina.authenticator.TestCallbackHandlerImpl",
|
||||
TestCallbackHandlerImpl.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultCallbackHandlerCreation() throws Exception {
|
||||
testCallbackHandlerCreation(null, CallbackHandlerImpl.class);
|
||||
}
|
||||
|
||||
|
||||
private void testCallbackHandlerCreation(String callbackHandlerImplClassName,
|
||||
Class<?> callbackHandlerImplClass)
|
||||
throws NoSuchMethodException, SecurityException, IllegalAccessException,
|
||||
IllegalArgumentException, InvocationTargetException {
|
||||
TestAuthenticator authenticator = new TestAuthenticator();
|
||||
authenticator.setJaspicCallbackHandlerClass(callbackHandlerImplClassName);
|
||||
Method createCallbackHandlerMethod =
|
||||
AuthenticatorBase.class.getDeclaredMethod("createCallbackHandler");
|
||||
createCallbackHandlerMethod.setAccessible(true);
|
||||
CallbackHandler callbackHandler =
|
||||
(CallbackHandler) createCallbackHandlerMethod.invoke(authenticator);
|
||||
Assert.assertTrue(callbackHandlerImplClass.isInstance(callbackHandler));
|
||||
}
|
||||
|
||||
private static class TestAuthenticator extends AuthenticatorBase {
|
||||
|
||||
@Override
|
||||
protected boolean doAuthenticate(Request request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAuthMethod() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class TestCallbackHandlerImpl implements CallbackHandler {
|
||||
|
||||
public TestCallbackHandlerImpl() {
|
||||
// Default constructor required by reflection
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||
// don't have to do anything; needed only for instantiation
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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.authenticator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.filters.TesterHttpServletResponse;
|
||||
import org.apache.catalina.startup.TesterMapRealm;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
|
||||
import org.apache.tomcat.util.security.MD5Encoder;
|
||||
|
||||
public class TesterDigestAuthenticatorPerformance {
|
||||
|
||||
private static String USER = "user";
|
||||
private static String PWD = "pwd";
|
||||
private static String ROLE = "role";
|
||||
private static String METHOD = "GET";
|
||||
private static String URI = "/protected";
|
||||
private static String CONTEXT_PATH = "/foo";
|
||||
private static String CLIENT_AUTH_HEADER = "authorization";
|
||||
private static String REALM = "TestRealm";
|
||||
private static String QOP = "auth";
|
||||
|
||||
private static final AtomicInteger nonceCount = new AtomicInteger(0);
|
||||
|
||||
private DigestAuthenticator authenticator = new DigestAuthenticator();
|
||||
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
doTest(4, 1000000);
|
||||
}
|
||||
|
||||
public void doTest(int threadCount, int requestCount) throws Exception {
|
||||
|
||||
TesterRunnable runnables[] = new TesterRunnable[threadCount];
|
||||
Thread threads[] = new Thread[threadCount];
|
||||
|
||||
String nonce = authenticator.generateNonce(new TesterDigestRequest());
|
||||
|
||||
// Create the runnables & threads
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
runnables[i] =
|
||||
new TesterRunnable(authenticator, nonce, requestCount);
|
||||
threads[i] = new Thread(runnables[i]);
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// Start the threads
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// Wait for the threads to finish
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
double wallTime = System.currentTimeMillis() - start;
|
||||
|
||||
// Gather the results...
|
||||
double totalTime = 0;
|
||||
int totalSuccess = 0;
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
System.out.println("Thread: " + i + " Success: " +
|
||||
runnables[i].getSuccess());
|
||||
totalSuccess = totalSuccess + runnables[i].getSuccess();
|
||||
totalTime = totalTime + runnables[i].getTime();
|
||||
}
|
||||
|
||||
System.out.println("Average time per request (user): " +
|
||||
totalTime/(threadCount * requestCount));
|
||||
System.out.println("Average time per request (wall): " +
|
||||
wallTime/(threadCount * requestCount));
|
||||
|
||||
Assert.assertEquals(((long)requestCount) * threadCount, totalSuccess);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
ConcurrentMessageDigest.init("MD5");
|
||||
|
||||
// Configure the Realm
|
||||
TesterMapRealm realm = new TesterMapRealm();
|
||||
realm.addUser(USER, PWD);
|
||||
realm.addUserRole(USER, ROLE);
|
||||
|
||||
// Add the Realm to the Context
|
||||
Context context = new StandardContext();
|
||||
context.setName(CONTEXT_PATH);
|
||||
context.setRealm(realm);
|
||||
|
||||
// Configure the Login config
|
||||
LoginConfig config = new LoginConfig();
|
||||
config.setRealmName(REALM);
|
||||
context.setLoginConfig(config);
|
||||
|
||||
// Make the Context and Realm visible to the Authenticator
|
||||
authenticator.setContainer(context);
|
||||
authenticator.setNonceCountWindowSize(8 * 1024);
|
||||
|
||||
authenticator.start();
|
||||
}
|
||||
|
||||
|
||||
private static class TesterRunnable implements Runnable {
|
||||
|
||||
private String nonce;
|
||||
private int requestCount;
|
||||
|
||||
private int success = 0;
|
||||
private long time = 0;
|
||||
|
||||
private TesterDigestRequest request;
|
||||
private HttpServletResponse response;
|
||||
private DigestAuthenticator authenticator;
|
||||
|
||||
private static final String A1 = USER + ":" + REALM + ":" + PWD;
|
||||
private static final String A2 = METHOD + ":" + CONTEXT_PATH + URI;
|
||||
|
||||
private static final String MD5A1 = MD5Encoder.encode(
|
||||
ConcurrentMessageDigest.digest("MD5", A1.getBytes()));
|
||||
private static final String MD5A2 = MD5Encoder.encode(
|
||||
ConcurrentMessageDigest.digest("MD5", A2.getBytes()));
|
||||
|
||||
|
||||
|
||||
// All init code should be in here. run() needs to be quick
|
||||
public TesterRunnable(DigestAuthenticator authenticator,
|
||||
String nonce, int requestCount) throws Exception {
|
||||
this.authenticator = authenticator;
|
||||
this.nonce = nonce;
|
||||
this.requestCount = requestCount;
|
||||
|
||||
request = new TesterDigestRequest();
|
||||
request.getMappingData().context = authenticator.context;
|
||||
|
||||
response = new TesterHttpServletResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < requestCount; i++) {
|
||||
try {
|
||||
request.setAuthHeader(buildDigestResponse(nonce));
|
||||
if (authenticator.authenticate(request, response)) {
|
||||
success++;
|
||||
}
|
||||
// Clear out authenticated user ready for next iteration
|
||||
request.setUserPrincipal(null);
|
||||
} catch (IOException ioe) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
time = System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
public int getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
private String buildDigestResponse(String nonce) {
|
||||
|
||||
String ncString = String.format("%1$08x",
|
||||
Integer.valueOf(nonceCount.incrementAndGet()));
|
||||
String cnonce = "cnonce";
|
||||
|
||||
String response = MD5A1 + ":" + nonce + ":" + ncString + ":" +
|
||||
cnonce + ":" + QOP + ":" + MD5A2;
|
||||
|
||||
String md5response = MD5Encoder.encode(
|
||||
ConcurrentMessageDigest.digest("MD5", response.getBytes()));
|
||||
|
||||
StringBuilder auth = new StringBuilder();
|
||||
auth.append("Digest username=\"");
|
||||
auth.append(USER);
|
||||
auth.append("\", realm=\"");
|
||||
auth.append(REALM);
|
||||
auth.append("\", nonce=\"");
|
||||
auth.append(nonce);
|
||||
auth.append("\", uri=\"");
|
||||
auth.append(CONTEXT_PATH + URI);
|
||||
auth.append("\", opaque=\"");
|
||||
auth.append(authenticator.getOpaque());
|
||||
auth.append("\", response=\"");
|
||||
auth.append(md5response);
|
||||
auth.append("\"");
|
||||
auth.append(", qop=");
|
||||
auth.append(QOP);
|
||||
auth.append(", nc=");
|
||||
auth.append(ncString);
|
||||
auth.append(", cnonce=\"");
|
||||
auth.append(cnonce);
|
||||
auth.append("\"");
|
||||
|
||||
return auth.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TesterDigestRequest extends Request {
|
||||
|
||||
private String authHeader = null;
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
public void setAuthHeader(String authHeader) {
|
||||
this.authHeader = authHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
if (CLIENT_AUTH_HEADER.equalsIgnoreCase(name)) {
|
||||
return authHeader;
|
||||
} else {
|
||||
return super.getHeader(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestURI() {
|
||||
return CONTEXT_PATH + URI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.apache.coyote.Request getCoyoteRequest() {
|
||||
return new org.apache.coyote.Request();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* 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.authenticator.jaspic;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.message.config.AuthConfigFactory;
|
||||
import javax.security.auth.message.config.AuthConfigProvider;
|
||||
import javax.security.auth.message.config.RegistrationListener;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Globals;
|
||||
|
||||
public class TestAuthConfigFactoryImpl {
|
||||
|
||||
private String oldCatalinaBase;
|
||||
private static final File TEST_CONFIG_FILE = new File("test/conf/jaspic-providers.xml");
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullLayer() {
|
||||
doTestResistration(null, "AC_1", ":AC_1");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullAppContext() {
|
||||
doTestResistration("L_1", null, "L_1:");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullLayerAndNullAppContext() {
|
||||
doTestResistration(null, null, ":");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchNoMatch01() {
|
||||
doTestSearchOrder("foo", "bar", 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchNoMatch02() {
|
||||
doTestSearchOrder(null, "bar", 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchNoMatch03() {
|
||||
doTestSearchOrder("foo", null, 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchNoMatch04() {
|
||||
doTestSearchOrder(null, null, 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyAppContextMatch01() {
|
||||
doTestSearchOrder("foo", "AC_1", 2);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyAppContextMatch02() {
|
||||
doTestSearchOrder(null, "AC_1", 2);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyAppContextMatch03() {
|
||||
doTestSearchOrder("L_2", "AC_1", 2);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyLayerMatch01() {
|
||||
doTestSearchOrder("L_1", "bar", 3);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyLayerMatch02() {
|
||||
doTestSearchOrder("L_1", null, 3);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOnlyLayerMatch03() {
|
||||
doTestSearchOrder("L_1", "AC_2", 3);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchBothMatch() {
|
||||
doTestSearchOrder("L_2", "AC_2", 4);
|
||||
}
|
||||
|
||||
|
||||
private void doTestSearchOrder(String layer, String appContext, int expected) {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp1, null, null, "1");
|
||||
AuthConfigProvider acp2 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp2, null, "AC_1", "2");
|
||||
AuthConfigProvider acp3 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp3, "L_1", null, "3");
|
||||
AuthConfigProvider acp4 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp4, "L_2", "AC_2", "4");
|
||||
|
||||
AuthConfigProvider searchResult = factory.getConfigProvider(layer, appContext, null);
|
||||
int searchIndex;
|
||||
if (searchResult == acp1) {
|
||||
searchIndex = 1;
|
||||
} else if (searchResult == acp2) {
|
||||
searchIndex = 2;
|
||||
} else if (searchResult == acp3) {
|
||||
searchIndex = 3;
|
||||
} else if (searchResult == acp4) {
|
||||
searchIndex = 4;
|
||||
} else {
|
||||
searchIndex = -1;
|
||||
}
|
||||
Assert.assertEquals(expected, searchIndex);
|
||||
}
|
||||
|
||||
|
||||
private void doTestResistration(String layer, String appContext, String expectedRegId) {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
SimpleRegistrationListener listener = new SimpleRegistrationListener(layer, appContext);
|
||||
|
||||
String regId = factory.registerConfigProvider(acp1, layer, appContext, null);
|
||||
Assert.assertEquals(expectedRegId, regId);
|
||||
|
||||
factory.getConfigProvider(layer, appContext, listener);
|
||||
factory.removeRegistration(regId);
|
||||
Assert.assertTrue(listener.wasCorrectlyCalled());
|
||||
|
||||
listener.reset();
|
||||
factory.registerConfigProvider(acp1, layer, appContext, null);
|
||||
factory.getConfigProvider(layer, appContext, listener);
|
||||
// Replace it
|
||||
AuthConfigProvider acp2 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp2, layer, appContext, null);
|
||||
Assert.assertTrue(listener.wasCorrectlyCalled());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertExact01() {
|
||||
doTestRegistrationInsert("L_3", "AC_2", "L_3", "AC_2");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertExact02() {
|
||||
doTestRegistrationInsert("L_2", "AC_3", "L_2", "AC_3");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertExact03() {
|
||||
doTestRegistrationInsert("L_4", "AC_4", "L_4", "AC_4");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertAppContext01() {
|
||||
doTestRegistrationInsert(null, "AC_3", "L_2", "AC_3");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertAppContext02() {
|
||||
doTestRegistrationInsert(null, "AC_4", "L_4", "AC_4");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationInsertLayer01() {
|
||||
doTestRegistrationInsert("L_4", null, "L_4", "AC_4");
|
||||
}
|
||||
|
||||
|
||||
private void doTestRegistrationInsert(String newLayer, String newAppContext,
|
||||
String expectedListenerLayer, String expectedListenerAppContext) {
|
||||
// Set up
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp1, "L_1", "AC_1", null);
|
||||
AuthConfigProvider acp2 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp2, null, "AC_2", null);
|
||||
AuthConfigProvider acp3 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp3, "L_2", null, null);
|
||||
AuthConfigProvider acp4 = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acp4, null, null, null);
|
||||
|
||||
SimpleRegistrationListener listener1 = new SimpleRegistrationListener("L_1", "AC_1");
|
||||
factory.getConfigProvider("L_1", "AC_1", listener1);
|
||||
SimpleRegistrationListener listener2 = new SimpleRegistrationListener("L_3", "AC_2");
|
||||
factory.getConfigProvider("L_3", "AC_2", listener2);
|
||||
SimpleRegistrationListener listener3 = new SimpleRegistrationListener("L_2", "AC_3");
|
||||
factory.getConfigProvider("L_2", "AC_3", listener3);
|
||||
SimpleRegistrationListener listener4 = new SimpleRegistrationListener("L_4", "AC_4");
|
||||
factory.getConfigProvider("L_4", "AC_4", listener4);
|
||||
|
||||
List<SimpleRegistrationListener> listeners = new ArrayList<>();
|
||||
listeners.add(listener1);
|
||||
listeners.add(listener2);
|
||||
listeners.add(listener3);
|
||||
listeners.add(listener4);
|
||||
|
||||
// Register a new provider that will impact some existing registrations
|
||||
AuthConfigProvider acpNew = new SimpleAuthConfigProvider(null, null);
|
||||
factory.registerConfigProvider(acpNew, newLayer, newAppContext, null);
|
||||
|
||||
// Check to see if the expected listener fired.
|
||||
for (SimpleRegistrationListener listener : listeners) {
|
||||
if (listener.wasCalled()) {
|
||||
Assert.assertEquals(listener.layer, expectedListenerLayer);
|
||||
Assert.assertEquals(listener.appContext, expectedListenerAppContext);
|
||||
Assert.assertTrue(listener.wasCorrectlyCalled());
|
||||
} else {
|
||||
Assert.assertFalse((listener.layer.equals(expectedListenerLayer) &&
|
||||
listener.appContext.equals(expectedListenerAppContext)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDetachListenerNonexistingRegistration() {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
String registrationId = factory.registerConfigProvider(acp1, "L_1", "AC_1", null);
|
||||
|
||||
SimpleRegistrationListener listener1 = new SimpleRegistrationListener("L_1", "AC_1");
|
||||
factory.getConfigProvider("L_1", "AC_1", listener1);
|
||||
|
||||
factory.removeRegistration(registrationId);
|
||||
String[] registrationIds = factory.detachListener(listener1, "L_1", "AC_1");
|
||||
Assert.assertTrue(registrationIds.length == 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDetachListener() {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
String registrationId = factory.registerConfigProvider(acp1, "L_1", "AC_1", null);
|
||||
|
||||
SimpleRegistrationListener listener1 = new SimpleRegistrationListener("L_1", "AC_1");
|
||||
factory.getConfigProvider("L_1", "AC_1", listener1);
|
||||
|
||||
String[] registrationIds = factory.detachListener(listener1, "L_1", "AC_1");
|
||||
Assert.assertTrue(registrationIds.length == 1);
|
||||
Assert.assertEquals(registrationId, registrationIds[0]);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullListener() {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
String registrationId = factory.registerConfigProvider(acp1, "L_1", "AC_1", null);
|
||||
|
||||
factory.getConfigProvider("L_1", "AC_1", null);
|
||||
|
||||
boolean result = factory.removeRegistration(registrationId);
|
||||
Assert.assertTrue(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAllRegistrationIds() {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
AuthConfigProvider acp1 = new SimpleAuthConfigProvider(null, null);
|
||||
String registrationId1 = factory.registerConfigProvider(acp1, "L_1", "AC_1", null);
|
||||
AuthConfigProvider acp2 = new SimpleAuthConfigProvider(null, null);
|
||||
String registrationId2 = factory.registerConfigProvider(acp2, "L_2", "AC_2", null);
|
||||
|
||||
String[] registrationIds = factory.getRegistrationIDs(null);
|
||||
Assert.assertTrue(registrationIds.length == 2);
|
||||
Set<String> ids = new HashSet<>(Arrays.asList(registrationIds));
|
||||
Assert.assertTrue(ids.contains(registrationId1));
|
||||
Assert.assertTrue(ids.contains(registrationId2));
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// set CATALINA_BASE to test so that the file with persistent providers will be written in test/conf folder
|
||||
oldCatalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP);
|
||||
System.setProperty(Globals.CATALINA_BASE_PROP, "test");
|
||||
|
||||
if (TEST_CONFIG_FILE.exists()) {
|
||||
if (!TEST_CONFIG_FILE.delete()) {
|
||||
Assert.fail("Failed to delete " + TEST_CONFIG_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
if (oldCatalinaBase != null ) {
|
||||
System.setProperty(Globals.CATALINA_BASE_PROP, oldCatalinaBase);
|
||||
} else {
|
||||
System.clearProperty(Globals.CATALINA_BASE_PROP);
|
||||
}
|
||||
|
||||
if (TEST_CONFIG_FILE.exists()) {
|
||||
if (!TEST_CONFIG_FILE.delete()) {
|
||||
Assert.fail("Failed to delete " + TEST_CONFIG_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemovePersistentRegistration() {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
factory.registerConfigProvider(
|
||||
SimpleAuthConfigProvider.class.getName(), null, "L_1", "AC_1", null);
|
||||
String registrationId2 = factory.registerConfigProvider(
|
||||
SimpleAuthConfigProvider.class.getName(), null, "L_2", "AC_2", null);
|
||||
|
||||
factory.removeRegistration(registrationId2);
|
||||
factory.refresh();
|
||||
|
||||
String[] registrationIds = factory.getRegistrationIDs(null);
|
||||
for (String registrationId : registrationIds) {
|
||||
Assert.assertNotEquals(registrationId2, registrationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullClassName() {
|
||||
doTestNullClassName(false, "L_1", "AC_1");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullClassOverrideExisting() {
|
||||
doTestNullClassName(true, "L_1", "AC_1");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegistrationNullClassNullLayerNullAppContext() {
|
||||
doTestNullClassName(false, null, null);
|
||||
}
|
||||
|
||||
|
||||
private void doTestNullClassName(boolean shouldOverrideExistingProvider, String layer, String appContext) {
|
||||
AuthConfigFactory factory = new AuthConfigFactoryImpl();
|
||||
if (shouldOverrideExistingProvider) {
|
||||
factory.registerConfigProvider(SimpleAuthConfigProvider.class.getName(), null, layer, appContext, null);
|
||||
}
|
||||
String registrationId = factory.registerConfigProvider(null, null, layer, appContext, null);
|
||||
factory.refresh();
|
||||
|
||||
String[] registrationIds = factory.getRegistrationIDs(null);
|
||||
Set<String> ids = new HashSet<>(Arrays.asList(registrationIds));
|
||||
Assert.assertTrue(ids.contains(registrationId));
|
||||
AuthConfigProvider provider = factory.getConfigProvider(layer, appContext, null);
|
||||
Assert.assertNull(provider);
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleRegistrationListener implements RegistrationListener {
|
||||
|
||||
private final String layer;
|
||||
private final String appContext;
|
||||
|
||||
private boolean called = false;
|
||||
private String layerNotified;
|
||||
private String appContextNotified;
|
||||
|
||||
public SimpleRegistrationListener(String layer, String appContext) {
|
||||
this.layer = layer;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(String layer, String appContext) {
|
||||
called = true;
|
||||
layerNotified = layer;
|
||||
appContextNotified = appContext;
|
||||
}
|
||||
|
||||
|
||||
public boolean wasCalled() {
|
||||
return called;
|
||||
}
|
||||
|
||||
|
||||
public boolean wasCorrectlyCalled() {
|
||||
return called && areTheSame(layer, layerNotified) &&
|
||||
areTheSame(appContext, appContextNotified);
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
called = false;
|
||||
layerNotified = null;
|
||||
appContextNotified = null;
|
||||
}
|
||||
|
||||
|
||||
private static boolean areTheSame(String a, String b) {
|
||||
if (a == null) {
|
||||
return b == null;
|
||||
}
|
||||
return a.equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.authenticator.jaspic;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Provider;
|
||||
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Providers;
|
||||
|
||||
public class TestPersistentProviderRegistrations {
|
||||
|
||||
@Test
|
||||
public void testLoadEmpty() {
|
||||
File f = new File("test/conf/jaspic-test-01.xml");
|
||||
Providers result = PersistentProviderRegistrations.loadProviders(f);
|
||||
Assert.assertEquals(0, result.getProviders().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLoadSimple() {
|
||||
File f = new File("test/conf/jaspic-test-02.xml");
|
||||
Providers result = PersistentProviderRegistrations.loadProviders(f);
|
||||
validateSimple(result);
|
||||
}
|
||||
|
||||
|
||||
private void validateSimple(Providers providers) {
|
||||
Assert.assertEquals(1, providers.getProviders().size());
|
||||
Provider p = providers.getProviders().get(0);
|
||||
Assert.assertEquals("a", p.getClassName());
|
||||
Assert.assertEquals("b", p.getLayer());
|
||||
Assert.assertEquals("c", p.getAppContext());
|
||||
Assert.assertEquals("d", p.getDescription());
|
||||
|
||||
Assert.assertEquals(2, p.getProperties().size());
|
||||
Assert.assertEquals("f", p.getProperties().get("e"));
|
||||
Assert.assertEquals("h", p.getProperties().get("g"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSaveSimple() {
|
||||
File f = new File("test/conf/jaspic-test-03.xml");
|
||||
if (f.exists()) {
|
||||
Assert.assertTrue(f.delete());
|
||||
}
|
||||
|
||||
// Create a config and write it out
|
||||
Providers start = new Providers();
|
||||
Provider p = new Provider();
|
||||
p.setClassName("a");
|
||||
p.setLayer("b");
|
||||
p.setAppContext("c");
|
||||
p.setDescription("d");
|
||||
p.addProperty("e", "f");
|
||||
p.addProperty("g", "h");
|
||||
start.addProvider(p);
|
||||
PersistentProviderRegistrations.writeProviders(start, f);
|
||||
|
||||
// Read it back
|
||||
Providers end = PersistentProviderRegistrations.loadProviders(f);
|
||||
|
||||
validateSimple(end);
|
||||
|
||||
if (f.exists()) {
|
||||
Assert.assertTrue("Failed to clean up [" + f + "]", f.delete());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLoadProviderWithoutLayerAndAC() {
|
||||
File f = new File("test/conf/jaspic-test-04.xml");
|
||||
Providers providers = PersistentProviderRegistrations.loadProviders(f);
|
||||
validateNoLayerAndAC(providers);
|
||||
}
|
||||
|
||||
|
||||
private void validateNoLayerAndAC(Providers providers) {
|
||||
Assert.assertEquals(1, providers.getProviders().size());
|
||||
Provider p = providers.getProviders().get(0);
|
||||
Assert.assertEquals("a", p.getClassName());
|
||||
Assert.assertNull(p.getLayer());
|
||||
Assert.assertNull(p.getAppContext());
|
||||
Assert.assertEquals("d", p.getDescription());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSaveProviderWithoutLayerAndAC() {
|
||||
File f = new File("test/conf/jaspic-test-05.xml");
|
||||
if (f.exists()) {
|
||||
Assert.assertTrue(f.delete());
|
||||
}
|
||||
|
||||
// Create a config and write it out
|
||||
Providers initialProviders = new Providers();
|
||||
Provider p = new Provider();
|
||||
p.setClassName("a");
|
||||
p.setDescription("d");
|
||||
initialProviders.addProvider(p);
|
||||
PersistentProviderRegistrations.writeProviders(initialProviders, f);
|
||||
|
||||
// Read it back
|
||||
Providers loadedProviders = PersistentProviderRegistrations.loadProviders(f);
|
||||
|
||||
try {
|
||||
validateNoLayerAndAC(loadedProviders);
|
||||
} finally {
|
||||
if (f.exists()) {
|
||||
if (!f.delete()) {
|
||||
Assert.fail("Failed to delete " + f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.authenticator.jaspic;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.message.AuthException;
|
||||
import javax.security.auth.message.MessageInfo;
|
||||
import javax.security.auth.message.config.ServerAuthConfig;
|
||||
import javax.security.auth.message.config.ServerAuthContext;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestSimpleServerAuthConfig {
|
||||
|
||||
private static final String SERVER_AUTH_MODULE_KEY_PREFIX =
|
||||
"org.apache.catalina.authenticator.jaspic.ServerAuthModule.";
|
||||
|
||||
private static final Map<String,String> CONFIG_PROPERTIES;
|
||||
static {
|
||||
CONFIG_PROPERTIES = new HashMap<>();
|
||||
CONFIG_PROPERTIES.put(SERVER_AUTH_MODULE_KEY_PREFIX + "1",
|
||||
TesterServerAuthModuleA.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigOnServerAuthConfig() throws Exception {
|
||||
ServerAuthConfig serverAuthConfig =
|
||||
new SimpleServerAuthConfig(null, null, null, CONFIG_PROPERTIES);
|
||||
ServerAuthContext serverAuthContext = serverAuthConfig.getAuthContext(null, null, null);
|
||||
|
||||
validateServerAuthContext(serverAuthContext);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConfigOnGetAuthContext() throws Exception {
|
||||
ServerAuthConfig serverAuthConfig = new SimpleServerAuthConfig(null, null, null, null);
|
||||
ServerAuthContext serverAuthContext =
|
||||
serverAuthConfig.getAuthContext(null, null, CONFIG_PROPERTIES);
|
||||
|
||||
validateServerAuthContext(serverAuthContext);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=AuthException.class)
|
||||
public void testConfigNone() throws Exception {
|
||||
ServerAuthConfig serverAuthConfig = new SimpleServerAuthConfig(null, null, null, null);
|
||||
serverAuthConfig.getAuthContext(null, null, null);
|
||||
}
|
||||
|
||||
|
||||
private void validateServerAuthContext(ServerAuthContext serverAuthContext) throws Exception {
|
||||
MessageInfo msgInfo = new TesterMessageInfo();
|
||||
serverAuthContext.cleanSubject(msgInfo, null);
|
||||
Assert.assertEquals("init()-cleanSubject()-", msgInfo.getMap().get("trace"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.authenticator.jaspic;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.message.MessageInfo;
|
||||
|
||||
public class TesterMessageInfo implements MessageInfo {
|
||||
|
||||
private Object requestMessage;
|
||||
private Object responseMessage;
|
||||
private final Map<String,String> map = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Object getRequestMessage() {
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getResponseMessage() {
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestMessage(Object request) {
|
||||
requestMessage = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResponseMessage(Object response) {
|
||||
responseMessage = response;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public Map getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.authenticator.jaspic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.message.AuthException;
|
||||
import javax.security.auth.message.AuthStatus;
|
||||
import javax.security.auth.message.MessageInfo;
|
||||
import javax.security.auth.message.MessagePolicy;
|
||||
import javax.security.auth.message.module.ServerAuthModule;
|
||||
|
||||
public class TesterServerAuthModuleA implements ServerAuthModule {
|
||||
|
||||
private StringBuilder trace = new StringBuilder("init()-");
|
||||
|
||||
@Override
|
||||
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject,
|
||||
Subject serviceSubject) throws AuthException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject)
|
||||
throws AuthException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException {
|
||||
trace.append("cleanSubject()-");
|
||||
messageInfo.getMap().put("trace", trace.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy,
|
||||
CallbackHandler handler, @SuppressWarnings("rawtypes") Map options)
|
||||
throws AuthException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public Class[] getSupportedMessageTypes() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.servlets.WebdavServlet;
|
||||
import org.apache.catalina.startup.TesterServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
/**
|
||||
* Test cases for {@link Connector}.
|
||||
*/
|
||||
public class TestConnector extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testStop() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w =
|
||||
Tomcat.addServlet(root, "tester", new TesterServlet());
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/", "tester");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
|
||||
rc = -1;
|
||||
bc.recycle();
|
||||
|
||||
connector.stop();
|
||||
|
||||
try {
|
||||
rc = getUrl("http://localhost:" + getPort() + "/", bc, 1000,
|
||||
null, null);
|
||||
} catch (SocketTimeoutException ste) {
|
||||
// May also see this with NIO
|
||||
// Make sure the test passes if we do
|
||||
rc = 503;
|
||||
}
|
||||
Assert.assertEquals(503, rc);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPort() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Connector connector1 = tomcat.getConnector();
|
||||
connector1.setPort(0);
|
||||
|
||||
Connector connector2 = new Connector();
|
||||
connector2.setPort(0);
|
||||
|
||||
tomcat.getService().addConnector(connector2);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int localPort1 = connector1.getLocalPort();
|
||||
int localPort2 = connector2.getLocalPort();
|
||||
|
||||
Assert.assertTrue(localPort1 > 0);
|
||||
Assert.assertTrue(localPort2 > 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
private void doTestTrace(Servlet servlet, boolean allowTrace) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context root = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(root, "default", servlet);
|
||||
root.addServletMappingDecoded("/", "default");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
connector.setAllowTrace(allowTrace);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String,List<String>> respHeaders = new HashMap<>();
|
||||
int rc = methodUrl("http://localhost:" + getPort() + "/index.html",
|
||||
bc, 30000, null, respHeaders, "OPTIONS");
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
|
||||
boolean foundTrace = false;
|
||||
for (String header : respHeaders.get("Allow")) {
|
||||
if (header.contains("TRACE")) {
|
||||
foundTrace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowTrace) {
|
||||
Assert.assertTrue(foundTrace);
|
||||
} else {
|
||||
Assert.assertFalse(foundTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.MessageBytes;
|
||||
|
||||
public class TestCoyoteAdapter extends TomcatBaseTest {
|
||||
|
||||
public static final String TEXT_8K;
|
||||
public static final byte[] BYTES_8K;
|
||||
|
||||
static {
|
||||
StringBuilder sb = new StringBuilder(8192);
|
||||
for (int i = 0; i < 512; i++) {
|
||||
sb.append("0123456789ABCDEF");
|
||||
}
|
||||
TEXT_8K = sb.toString();
|
||||
BYTES_8K = TEXT_8K.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@Test
|
||||
public void testPathParmsRootNone() throws Exception {
|
||||
pathParamTest("/", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooNone() throws Exception {
|
||||
pathParamTest("/foo", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsRootSessionOnly() throws Exception {
|
||||
pathParamTest("/;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionOnly() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummy() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummyValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionBar() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamsRedirect() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Must have a real docBase. Don't use java.io.tmpdir as it may not be
|
||||
// writable.
|
||||
File docBase = new File(getTemporaryDirectory(), "testCoyoteAdapter");
|
||||
addDeleteOnTearDown(docBase);
|
||||
if (!docBase.mkdirs() && !docBase.isDirectory()) {
|
||||
Assert.fail("Failed to create: [" + docBase.toString() + "]");
|
||||
}
|
||||
|
||||
// Create the folder that will trigger the redirect
|
||||
File foo = new File(docBase, "foo");
|
||||
addDeleteOnTearDown(foo);
|
||||
if (!foo.mkdirs() && !foo.isDirectory()) {
|
||||
Assert.fail("Unable to create foo directory in docBase");
|
||||
}
|
||||
|
||||
Context ctx = tomcat.addContext("", docBase.getAbsolutePath());
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
testPath("/", "none");
|
||||
testPath("/;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234;=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
private void pathParamTest(String path, String expected) throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private void testPath(String path, String expected) throws Exception {
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private static class PathParamServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
String sessionId = req.getRequestedSessionId();
|
||||
if (sessionId == null) {
|
||||
sessionId = "none";
|
||||
}
|
||||
pw.write(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtRootNoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1NoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1WithParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah;x=y/blah.txt", "none");
|
||||
}
|
||||
|
||||
private void pathParamExtensionTest(String path, String expected)
|
||||
throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("/testapp", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("*.txt", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602a() throws Exception {
|
||||
// No UTF-8
|
||||
doTestUriDecoding("/foo", "UTF-8", "/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602b() throws Exception {
|
||||
// Valid UTF-8
|
||||
doTestUriDecoding("/foo%c4%87", "UTF-8", "/foo\u0107");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602c() throws Exception {
|
||||
// Partial UTF-8
|
||||
doTestUriDecoding("/foo%c4", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602d() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ff", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602e() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ed%a0%80", "UTF-8", "/foo\uFFFD\uFFFD\uFFFD");
|
||||
}
|
||||
|
||||
private void doTestUriDecoding(String path, String encoding,
|
||||
String expectedPathInfo) throws Exception{
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
tomcat.getConnector().setURIEncoding(encoding);
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
PathInfoServlet servlet = new PathInfoServlet();
|
||||
Tomcat.addServlet(ctx, "servlet", servlet);
|
||||
ctx.addServletMappingDecoded("/*", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + path,
|
||||
new ByteChunk(), null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
|
||||
Assert.assertEquals(expectedPathInfo, servlet.getPathInfo());
|
||||
}
|
||||
|
||||
private static class PathInfoServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private volatile String pathInfo = null;
|
||||
|
||||
public String getPathInfo() {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Not thread safe. Concurrent requests to this servlet will
|
||||
// over-write all the results but the last processed.
|
||||
pathInfo = req.getPathInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug54928() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
AsyncServlet servlet = new AsyncServlet();
|
||||
Wrapper w = Tomcat.addServlet(ctx, "async", servlet);
|
||||
w.setAsyncSupported(true);
|
||||
ctx.addServletMappingDecoded("/async", "async");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
SimpleHttpClient client = new SimpleHttpClient() {
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String request = "GET /async HTTP/1.1" + SimpleHttpClient.CRLF +
|
||||
"Host: a" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF;
|
||||
|
||||
client.setPort(getPort());
|
||||
client.setRequest(new String[] {request});
|
||||
|
||||
client.connect();
|
||||
client.sendRequest();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
String line = client.readLine();
|
||||
if (line != null && line.length() > 20) {
|
||||
log.info(line.subSequence(0, 20) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect();
|
||||
|
||||
// Wait for server thread to stop
|
||||
Thread t = servlet.getThread();
|
||||
long startTime = System.nanoTime();
|
||||
t.join(5000);
|
||||
long endTime = System.nanoTime();
|
||||
log.info("Waited for servlet thread to stop for "
|
||||
+ (endTime - startTime) / 1000000 + " ms");
|
||||
|
||||
Assert.assertTrue(servlet.isCompleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalize01() {
|
||||
doTestNormalize("/foo/../bar", "/bar");
|
||||
}
|
||||
|
||||
private void doTestNormalize(String input, String expected) {
|
||||
MessageBytes mb = MessageBytes.newInstance();
|
||||
byte[] b = input.getBytes(StandardCharsets.UTF_8);
|
||||
mb.setBytes(b, 0, b.length);
|
||||
|
||||
boolean result = CoyoteAdapter.normalize(mb);
|
||||
mb.toString();
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertFalse(result);
|
||||
} else {
|
||||
Assert.assertTrue(result);
|
||||
Assert.assertEquals(expected, mb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AsyncServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// This is a hack that won't work generally as servlets are expected to
|
||||
// handle more than one request.
|
||||
private Thread t;
|
||||
private volatile boolean completed = false;
|
||||
|
||||
public Thread getThread() {
|
||||
return t;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
final OutputStream os = resp.getOutputStream();
|
||||
|
||||
final AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(3000);
|
||||
|
||||
t = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
try {
|
||||
// Some tests depend on this write failing (e.g.
|
||||
// because the client has gone away). In some cases
|
||||
// there may be a large (ish) buffer to fill before
|
||||
// the write fails.
|
||||
for (int j = 0 ; j < 8; j++) {
|
||||
os.write(BYTES_8K);
|
||||
}
|
||||
os.flush();
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("Exception caught " + e);
|
||||
try {
|
||||
// Note if request times out before this
|
||||
// exception is thrown and the complete call
|
||||
// below is made, the complete call below will
|
||||
// fail since the timeout will have completed
|
||||
// the request.
|
||||
asyncCtxt.complete();
|
||||
break;
|
||||
} finally {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
t.setName("testBug54928");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterData;
|
||||
|
||||
/*
|
||||
* Various requests, usually originating from fuzzing, that have triggered an
|
||||
* incorrect response from Tomcat - usually a 500 response rather than a 400
|
||||
* response.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
|
||||
|
||||
private static final String VALUE_16K = TesterData.string('x', 16 * 1024);
|
||||
// Default max header count is 100
|
||||
private static final String HEADER_150 = TesterData.string("X-Tomcat-Test: a" + CRLF, 150);
|
||||
// Default max header count is 200 (need to keep under maxHeaderCount as well)
|
||||
private static final String COOKIE_250 = TesterData.string("Cookie: a=b;c=d;e=f;g=h" + CRLF, 75);
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: requestline[{0}], expected[{2}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
parameterSets.add(new Object[] { "GET /00 HTTP/1.1",
|
||||
"Host: lÿ#" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET *; HTTP/1.1",
|
||||
"Host: localhost" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /02 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
"Content-Length: \u00A0" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /03 HTTP/1.1",
|
||||
"Content-Length: 1" + CRLF +
|
||||
"Content-Length: 1" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /04 HTTP/1.1",
|
||||
"Transfer-Encoding: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /05 HTTP/1.1",
|
||||
"Expect: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /06 HTTP/1.1",
|
||||
"Connection: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /07 HTTP/1.1",
|
||||
"User-Agent: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /08 HTTP/1.1",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET http://host/09 HTTP/1.0",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /10 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
COOKIE_250,
|
||||
"400" } );
|
||||
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public String requestLine;
|
||||
|
||||
@Parameter(1)
|
||||
public String headers;
|
||||
|
||||
@Parameter(2)
|
||||
public String expected;
|
||||
|
||||
|
||||
@Test
|
||||
public void doTest() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
tomcat.getConnector().setAttribute("restrictedUserAgents", "value-not-important");
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName());
|
||||
ctxt.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Client client = new Client(tomcat.getConnector().getLocalPort());
|
||||
client.setRequest(new String[] {requestLine + CRLF, headers + CRLF});
|
||||
|
||||
client.connect();
|
||||
client.processRequest();
|
||||
|
||||
// Expected response
|
||||
String line = client.getResponseLine();
|
||||
Assert.assertTrue(line + CRLF + client.getResponseBody(), line.startsWith("HTTP/1.1 " + expected + " "));
|
||||
}
|
||||
|
||||
|
||||
private static final class Client extends SimpleHttpClient {
|
||||
|
||||
public Client(int port) {
|
||||
setPort(port);
|
||||
setRequestPause(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream createOutputStream(Socket socket) throws IOException {
|
||||
// Override the default implementation so we can create a large
|
||||
// enough buffer to hold the entire request.
|
||||
// The default implementation uses the 8k buffer in the
|
||||
// StreamEncoder. Since some requests are larger than this, those
|
||||
// requests will be sent in several parts. If the first part is
|
||||
// sufficient for Tomcat to determine the request is invalid, Tomcat
|
||||
// will close the connection, causing the write of the remaining
|
||||
// parts to fail which in turn causes the test to fail.
|
||||
return new BufferedOutputStream(super.createOutputStream(socket), 32 * 1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
// Response body varies. It is the response code that is of interest
|
||||
// in these tests.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteInputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testReadWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
String requestBody = "HelloWorld";
|
||||
int rc = postUrl(requestBody.getBytes(StandardCharsets.UTF_8),
|
||||
"http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertTrue(requestBody.equals(bc.toString()));
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteInputStream is = (CoyoteInputStream) req.getInputStream();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(256);
|
||||
is.read(buffer);
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
os.write(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteOutputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
ByteChunk expected = new ByteChunk();
|
||||
expected.append(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
Assert.assertTrue(expected.equals(bc));
|
||||
}
|
||||
}
|
||||
|
||||
private void doNonBlockingTest(int asyncWriteTarget, int syncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w = Tomcat.addServlet(root, "nbWrite",
|
||||
new NonBlockingWriteServlet(asyncWriteTarget, useContainerThreadToSetListener));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/nbWrite", "nbWrite");
|
||||
Tomcat.addServlet(root, "write",
|
||||
new BlockingWriteServlet(asyncWriteTarget, syncWriteTarget));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/write", "write");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
// Extend timeout to 5 mins for debugging
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/nbWrite", bc,
|
||||
300000, null, null);
|
||||
|
||||
int totalCount = asyncWriteTarget + syncWriteTarget;
|
||||
StringBuilder sb = new StringBuilder(totalCount * 16);
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
sb.append("OK - " + i + System.lineSeparator());
|
||||
}
|
||||
String expected = null;
|
||||
if (sb.length() > 0) {
|
||||
expected = sb.toString();
|
||||
}
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals(expected, bc.toString());
|
||||
}
|
||||
|
||||
private static final class NonBlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int asyncWriteTarget;
|
||||
private final AtomicInteger asyncWriteCount = new AtomicInteger(0);
|
||||
private final boolean useContainerThreadToSetListener;
|
||||
|
||||
public NonBlockingWriteServlet(int asyncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) {
|
||||
this.asyncWriteTarget = asyncWriteTarget;
|
||||
this.useContainerThreadToSetListener = useContainerThreadToSetListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
|
||||
AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(5);
|
||||
Runnable task = new AsyncTask(asyncCtxt, sos);
|
||||
if (useContainerThreadToSetListener) {
|
||||
asyncCtxt.start(task);
|
||||
} else {
|
||||
Thread t = new Thread(task);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void doAsyncWrite(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) throws IOException {
|
||||
while (sos.isReady()) {
|
||||
int next = asyncWriteCount.getAndIncrement();
|
||||
if (next < asyncWriteTarget) {
|
||||
sos.write(
|
||||
("OK - " + next + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
sos.flush();
|
||||
} else {
|
||||
asyncCtxt.dispatch("/write");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncTask implements Runnable {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public AsyncTask(AsyncContext asyncCtxt, ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sos.setWriteListener(new MyWriteListener(asyncCtxt, sos));
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyWriteListener implements WriteListener {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public MyWriteListener(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWritePossible() throws IOException {
|
||||
doAsyncWrite(asyncCtxt, sos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
// Not expected.
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int start;
|
||||
private final int len;
|
||||
|
||||
public BlockingWriteServlet(int start, int len) {
|
||||
this.start = start;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
for (int i = start; i < start + len; i++) {
|
||||
sos.write(("OK - " + i + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
os.write(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.TestUtf8;
|
||||
import org.apache.tomcat.util.buf.TestUtf8.Utf8TestCase;
|
||||
|
||||
public class TestInputBuffer extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testUtf8Body() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Echo", new Utf8Echo());
|
||||
root.addServletMappingDecoded("/test", "Echo");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
for (Utf8TestCase testCase : TestUtf8.TEST_CASES) {
|
||||
String expected = null;
|
||||
if (testCase.invalidIndex == -1) {
|
||||
expected = testCase.outputReplaced;
|
||||
}
|
||||
doUtf8BodyTest(testCase.description, testCase.input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60400() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Bug60400Servlet", new Bug60400Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug60400Servlet");
|
||||
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("socket.appReadBufSize", "9000"));
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
byte[] requestBody = new byte[9500];
|
||||
Arrays.fill(requestBody, (byte) 1);
|
||||
int rc = postUrl(requestBody, "http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(requestBody.length, bc.getLength());
|
||||
}
|
||||
|
||||
|
||||
private void doUtf8BodyTest(String description, int[] input,
|
||||
String expected) throws Exception {
|
||||
|
||||
byte[] bytes = new byte[input.length];
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
bytes[i] = (byte) input[i];
|
||||
}
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = postUrl(bytes, "http://localhost:" + getPort() + "/test", bc,
|
||||
null);
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertEquals(description, HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(description, "FAILED", bc.toString());
|
||||
} else if (expected.length() == 0) {
|
||||
Assert.assertNull(description, bc.toString());
|
||||
} else {
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(description, expected, bc.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8Echo extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Should use POST
|
||||
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
Reader r = req.getReader();
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
try {
|
||||
// Copy one character at a time
|
||||
int c = r.read();
|
||||
while (c != -1) {
|
||||
w.write(c);
|
||||
c = r.read();
|
||||
}
|
||||
w.close();
|
||||
} catch (MalformedInputException mie) {
|
||||
resp.resetBuffer();
|
||||
w.write("FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Bug60400Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (BufferedReader reader = req.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
}
|
||||
resp.getWriter().print(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestKeepAliveCount extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testHttp10() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp10Request();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttp11() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp11Request();
|
||||
}
|
||||
|
||||
|
||||
private class TestKeepAliveClient extends SimpleHttpClient {
|
||||
|
||||
|
||||
private boolean init;
|
||||
|
||||
private synchronized void init() {
|
||||
if (init) return;
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "5"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
init = true;
|
||||
}
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
processRequest(false); // blocks until response has been read
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
private void doHttp11Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.1" + CRLF +
|
||||
"Host: localhost" + CRLF +
|
||||
"Connection: Keep-Alive" + CRLF+
|
||||
"Keep-Alive: 300"+ CRLF+ CRLF;
|
||||
|
||||
setRequest(request);
|
||||
|
||||
for (int i=0; i<5; i++) {
|
||||
processRequest(false); // blocks until response has been read
|
||||
Assert.assertTrue(getResponseLine()!=null && getResponseLine().startsWith("HTTP/1.1 200 "));
|
||||
}
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestMaxConnections extends TomcatBaseTest {
|
||||
private static final int MAX_CONNECTIONS = 3;
|
||||
public static final int soTimeout = 5000;
|
||||
public static final int connectTimeout = 1000;
|
||||
|
||||
@Test
|
||||
public void testConnector() throws Exception {
|
||||
init();
|
||||
Assume.assumeFalse("This feature is not available for NIO2 (BZ58103)",
|
||||
getTomcatInstance().getConnector().getProtocolHandlerClassName().contains("Nio2"));
|
||||
ConnectThread[] t = new ConnectThread[10];
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i] = new ConnectThread();
|
||||
t[i].setName("ConnectThread["+i+"]");
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].start();
|
||||
Thread.sleep(50);
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].join();
|
||||
}
|
||||
|
||||
Assert.assertEquals(MAX_CONNECTIONS, SimpleServlet.getMaxConnections());
|
||||
}
|
||||
|
||||
private class ConnectThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
TestClient client = new TestClient();
|
||||
client.doHttp10Request();
|
||||
} catch (Exception x) {
|
||||
// NO-OP. Some connections are expected to fail.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private synchronized void init() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
StandardContext root = (StandardContext) tomcat.addContext("", SimpleHttpClient.TEMP_DIR);
|
||||
root.setUnloadDelay(soTimeout);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxThreads", "10"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxConnections", Integer.toString(MAX_CONNECTIONS)));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("acceptCount", "1"));
|
||||
tomcat.start();
|
||||
}
|
||||
|
||||
private class TestClient extends SimpleHttpClient {
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
setPort(getPort());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Open connection
|
||||
connect(connectTimeout,soTimeout);
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
boolean passed = false;
|
||||
processRequest(false); // blocks until response has been read
|
||||
long stop = System.currentTimeMillis();
|
||||
log.info(Thread.currentThread().getName()+" Request complete:"+(stop-start)+" ms.");
|
||||
passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static int currentConnections = 0;
|
||||
private static int maxConnections = 0;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
increment();
|
||||
|
||||
System.out.println("Processing thread: " + Thread.currentThread().getName());
|
||||
try {
|
||||
Thread.sleep(TestMaxConnections.soTimeout*4/5);
|
||||
} catch (InterruptedException x) {
|
||||
|
||||
}
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
|
||||
decrement();
|
||||
}
|
||||
|
||||
private static synchronized void increment() {
|
||||
currentConnections++;
|
||||
if (currentConnections > maxConnections) {
|
||||
maxConnections = currentConnections;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void decrement() {
|
||||
currentConnections--;
|
||||
}
|
||||
|
||||
|
||||
public static synchronized int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestOutputBuffer extends TomcatBaseTest{
|
||||
|
||||
/*
|
||||
* Expect that the buffered results are slightly slower since Tomcat now has
|
||||
* an internal buffer so an extra one just adds overhead.
|
||||
*
|
||||
* @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=52328"
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
WritingServlet servlet = new WritingServlet(i);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
int rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i, bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
|
||||
rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i + "?useBuffer=y", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug52577() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
Bug52577Servlet bug52577 = new Bug52577Servlet();
|
||||
Tomcat.addServlet(root, "bug52577", bug52577);
|
||||
root.addServletMappingDecoded("/", "bug52577");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
}
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
private final String writeString;
|
||||
private final int writeCount;
|
||||
|
||||
public WritingServlet(int writeLength) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < writeLength; i++) {
|
||||
sb.append('x');
|
||||
}
|
||||
writeString = sb.toString();
|
||||
writeCount = EXPECTED_CONTENT_LENGTH / writeLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
// Wrap with a buffer if necessary
|
||||
String useBufferStr = req.getParameter("useBuffer");
|
||||
if (useBufferStr != null) {
|
||||
w = new BufferedWriter(w);
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
for (int i = 0; i < writeCount; i++) {
|
||||
w.write(writeString);
|
||||
}
|
||||
if (useBufferStr != null) {
|
||||
w.flush();
|
||||
}
|
||||
long lastRunNano = System.nanoTime() - start;
|
||||
|
||||
System.out.println("Write length: " + writeString.length() +
|
||||
", Buffered: " + (useBufferStr == null ? "n" : "y") +
|
||||
", Time: " + lastRunNano + "ns");
|
||||
}
|
||||
}
|
||||
|
||||
private static class Bug52577Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
Writer w = resp.getWriter();
|
||||
w.write("OK");
|
||||
resp.resetBuffer();
|
||||
w.write("OK");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUtf8SurrogateBody() throws Exception {
|
||||
// Create test data. This is carefully constructed to trigger the edge
|
||||
// case. Small variations may cause the test to miss the edge case.
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("a");
|
||||
|
||||
for (int i = 0x10000; i < 0x11000; i++) {
|
||||
char[] chars = Character.toChars(i);
|
||||
sb.append(chars);
|
||||
}
|
||||
String data = sb.toString();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Test", new Utf8WriteChars(data));
|
||||
root.addServletMappingDecoded("/test", "Test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test", bc, null);
|
||||
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(data, bc.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8WriteChars extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final char[] chars;
|
||||
|
||||
public Utf8WriteChars(String data) {
|
||||
chars = data.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
w.write(chars[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
File diff suppressed because it is too large
Load Diff
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.startup.LoggingBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterRequest;
|
||||
|
||||
public class TestResponsePerformance extends LoggingBaseTest {
|
||||
|
||||
private final int ITERATIONS = 100000;
|
||||
|
||||
@Test
|
||||
public void testToAbsolutePerformance() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
// Warm up
|
||||
doHomebrew(resp);
|
||||
doUri();
|
||||
|
||||
// Note: Java 9 on my OSX laptop consistently shows doUri() is faster
|
||||
// than doHomebrew(). Worth a closer look for Tomcat 10 on the
|
||||
// assumption it will require java 9
|
||||
|
||||
// To allow for timing differences between runs, a "best of n" approach
|
||||
// is taken for this test
|
||||
final int bestOf = 5;
|
||||
final int winTarget = (bestOf + 1) / 2;
|
||||
int homebrewWin = 0;
|
||||
int count = 0;
|
||||
|
||||
while (count < bestOf && homebrewWin < winTarget) {
|
||||
long homebrew = doHomebrew(resp);
|
||||
long uri = doUri();
|
||||
log.info("Current 'home-brew': " + homebrew + "ms, Using URI: " + uri + "ms");
|
||||
if (homebrew < uri) {
|
||||
homebrewWin++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
Assert.assertTrue(homebrewWin == winTarget);
|
||||
}
|
||||
|
||||
|
||||
private long doHomebrew(Response resp) {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
resp.toAbsolute("bar.html");
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
|
||||
private long doUri() {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
URI base = URI.create(
|
||||
"http://localhost:8080/level1/level2/foo.html");
|
||||
base.resolve(URI.create("bar.html")).toASCIIString();
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
}
|
||||
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.connector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Globals;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestSendFile extends TomcatBaseTest {
|
||||
|
||||
public static final int ITERATIONS = 10;
|
||||
public static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
@Test
|
||||
public void testSendFile() throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
File[] files = new File[ITERATIONS];
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
files[i] = generateFile(TEMP_DIR, "-" + i, EXPECTED_CONTENT_LENGTH * (i + 1));
|
||||
}
|
||||
try {
|
||||
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
WritingServlet servlet = new WritingServlet(files[i]);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String, List<String>> respHeaders = new HashMap<>();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
long start = System.currentTimeMillis();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/servlet" + i, bc, null,
|
||||
respHeaders);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
System.out.println("Client received " + bc.getLength() + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
Assert.assertEquals(EXPECTED_CONTENT_LENGTH * (i + 1L), bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
} finally {
|
||||
for (File f : files) {
|
||||
Assert.assertTrue("Failed to clean up [" + f + "]", f.delete());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public File generateFile(String dir, String suffix, int size) throws IOException {
|
||||
String name = "testSendFile-" + System.currentTimeMillis() + suffix + ".txt";
|
||||
File f = new File(dir, name);
|
||||
try (FileWriter fw = new FileWriter(f, false); BufferedWriter w = new BufferedWriter(fw);) {
|
||||
int defSize = 8192;
|
||||
while (size > 0) {
|
||||
int bytes = Math.min(size, defSize);
|
||||
char[] b = new char[bytes];
|
||||
Arrays.fill(b, 'X');
|
||||
w.write(b);
|
||||
size = size - bytes;
|
||||
}
|
||||
w.flush();
|
||||
}
|
||||
System.out
|
||||
.println("Created file:" + f.getAbsolutePath() + " with " + f.length() + " bytes.");
|
||||
return f;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final File f;
|
||||
|
||||
public WritingServlet(File f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(f.length());
|
||||
if (Boolean.TRUE.equals(req.getAttribute(Globals.SENDFILE_SUPPORTED_ATTR))) {
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, f.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(f.length()));
|
||||
} else {
|
||||
byte[] c = new byte[8192];
|
||||
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(f))) {
|
||||
int len = 0;
|
||||
int written = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
do {
|
||||
len = in.read(c);
|
||||
if (len > 0) {
|
||||
resp.getOutputStream().write(c, 0, len);
|
||||
written += len;
|
||||
}
|
||||
} while (len > 0);
|
||||
System.out.println("Server Wrote " + written + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60409() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctx = tomcat.addContext("", TEMP_DIR);
|
||||
File file = generateFile(TEMP_DIR, "", EXPECTED_CONTENT_LENGTH);
|
||||
Tomcat.addServlet(ctx, "test", new Bug60409Servlet(file));
|
||||
ctx.addServletMappingDecoded("/", "test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test/?" + Globals.SENDFILE_SUPPORTED_ATTR
|
||||
+ "=true", bc, null);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<Throwable> exceptions = new ArrayList<>();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
|
||||
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (exceptions.size() > 0) {
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Bug60409Servlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final File file;
|
||||
|
||||
Bug60409Servlet(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
if (Boolean.valueOf(req.getParameter(Globals.SENDFILE_SUPPORTED_ATTR)).booleanValue()) {
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(file.length());
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, file.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(file.length()));
|
||||
if (!file.delete()) {
|
||||
throw new ServletException("Failed to delete [" + file + "]");
|
||||
}
|
||||
} else {
|
||||
byte[] c = new byte[1024];
|
||||
Random rd = new Random();
|
||||
rd.nextBytes(c);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
resp.getOutputStream().write(c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RequestExecutor implements Runnable {
|
||||
private final String url;
|
||||
private final CountDownLatch latch;
|
||||
private final List<Throwable> exceptions;
|
||||
|
||||
RequestExecutor(String url, CountDownLatch latch, List<Throwable> exceptions) {
|
||||
this.url = url;
|
||||
this.latch = latch;
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ByteChunk result = new ByteChunk();
|
||||
int rc = getUrl(url, result, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(1024, result.getLength());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
exceptions.add(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
test/org/apache/catalina/connector/test_content.txt
Normal file
19
test/org/apache/catalina/connector/test_content.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This is a test file for the WebappServiceLoader
|
||||
# It contains comment lines and blank lines
|
||||
|
||||
test content
|
||||
331
test/org/apache/catalina/core/TestApplicationContext.java
Normal file
331
test/org/apache/catalina/core/TestApplicationContext.java
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.descriptor.JspConfigDescriptor;
|
||||
import javax.servlet.descriptor.JspPropertyGroupDescriptor;
|
||||
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.Lifecycle;
|
||||
import org.apache.catalina.LifecycleEvent;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.LifecycleListener;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.Tomcat.FixContextListener;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestApplicationContext extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testBug53257() throws Exception {
|
||||
getTomcatInstanceTestWebapp(false, true);
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() +
|
||||
"/test/bug53257/index.jsp");
|
||||
|
||||
String result = res.toString();
|
||||
String[] lines = result.split("\n");
|
||||
for (String line : lines) {
|
||||
if (line.startsWith("FAIL")) {
|
||||
Assert.fail(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug53467() throws Exception {
|
||||
getTomcatInstanceTestWebapp(false, true);
|
||||
|
||||
ByteChunk res = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() +
|
||||
"/test/bug5nnnn/bug53467%5D.jsp", res, null);
|
||||
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertTrue(res.toString().contains("<p>OK</p>"));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddFilterWithFilterNameNull() throws LifecycleException {
|
||||
getServletContext().addFilter(null, (Filter) null);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddFilterWithFilterNameEmptyString() throws LifecycleException {
|
||||
getServletContext().addFilter("", (Filter) null);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddServletWithServletNameNull() throws LifecycleException {
|
||||
getServletContext().addServlet(null, (Servlet) null);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddServletWithServletNameEmptyString() throws LifecycleException {
|
||||
getServletContext().addServlet("", (Servlet) null);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetJspConfigDescriptor() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstanceTestWebapp(false, false);
|
||||
|
||||
StandardContext standardContext =
|
||||
(StandardContext) tomcat.getHost().findChildren()[0];
|
||||
|
||||
ServletContext servletContext = standardContext.getServletContext();
|
||||
|
||||
Assert.assertNull(servletContext.getJspConfigDescriptor());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Assert.assertNotNull(servletContext.getJspConfigDescriptor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJspPropertyGroupsAreIsolated() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstanceTestWebapp(false, false);
|
||||
|
||||
StandardContext standardContext =
|
||||
(StandardContext) tomcat.getHost().findChildren()[0];
|
||||
|
||||
ServletContext servletContext = standardContext.getServletContext();
|
||||
|
||||
Assert.assertNull(servletContext.getJspConfigDescriptor());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
JspConfigDescriptor jspConfigDescriptor =
|
||||
servletContext.getJspConfigDescriptor();
|
||||
Collection<JspPropertyGroupDescriptor> propertyGroups =
|
||||
jspConfigDescriptor.getJspPropertyGroups();
|
||||
Assert.assertFalse(propertyGroups.isEmpty());
|
||||
propertyGroups.clear();
|
||||
|
||||
jspConfigDescriptor = servletContext.getJspConfigDescriptor();
|
||||
propertyGroups = jspConfigDescriptor.getJspPropertyGroups();
|
||||
Assert.assertFalse(propertyGroups.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
private ServletContext getServletContext() throws LifecycleException {
|
||||
Tomcat tomcat = getTomcatInstanceTestWebapp(false, false);
|
||||
|
||||
StandardContext standardContext =
|
||||
(StandardContext) tomcat.getHost().findChildren()[0];
|
||||
|
||||
return standardContext.getServletContext();
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetInitParameter() throws Exception {
|
||||
getTomcatInstance().start();
|
||||
getServletContext().setInitParameter("name", "value");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Cross-context requests with parallel deployment
|
||||
*/
|
||||
@Test
|
||||
public void testBug57190() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context foo1 = new StandardContext();
|
||||
foo1.setName("/foo##1");
|
||||
foo1.setPath("/foo");
|
||||
foo1.setWebappVersion("1");
|
||||
foo1.addLifecycleListener(new FixContextListener());
|
||||
foo1.addLifecycleListener(new SetIdListener("foo1"));
|
||||
tomcat.getHost().addChild(foo1);
|
||||
|
||||
Context foo2 = new StandardContext();
|
||||
foo2.setName("/foo##2");
|
||||
foo2.setPath("/foo");
|
||||
foo2.setWebappVersion("2");
|
||||
foo2.addLifecycleListener(new FixContextListener());
|
||||
foo2.addLifecycleListener(new SetIdListener("foo2"));
|
||||
tomcat.getHost().addChild(foo2);
|
||||
|
||||
Context bar = tomcat.addContext("/bar", null);
|
||||
bar.addLifecycleListener(new SetIdListener("bar"));
|
||||
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
ctx.addLifecycleListener(new SetIdListener("ROOT"));
|
||||
ctx.setCrossContext(true);
|
||||
|
||||
Tomcat.addServlet(ctx, "Bug57190Servlet", new Bug57190Servlet());
|
||||
ctx.addServletMappingDecoded("/", "Bug57190Servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + "/");
|
||||
String body = res.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("01-bar"));
|
||||
Assert.assertTrue(body, body.contains("02-foo2"));
|
||||
Assert.assertTrue(body, body.contains("03-foo1"));
|
||||
Assert.assertTrue(body, body.contains("04-foo2"));
|
||||
Assert.assertTrue(body, body.contains("05-foo2"));
|
||||
Assert.assertTrue(body, body.contains("06-ROOT"));
|
||||
Assert.assertTrue(body, body.contains("07-ROOT"));
|
||||
Assert.assertTrue(body, body.contains("08-foo2"));
|
||||
Assert.assertTrue(body, body.contains("09-ROOT"));
|
||||
}
|
||||
|
||||
|
||||
private static class Bug57190Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
ServletContext sc = req.getServletContext();
|
||||
pw.println("01-" + sc.getContext("/bar").getInitParameter("id"));
|
||||
pw.println("02-" + sc.getContext("/foo").getInitParameter("id"));
|
||||
pw.println("03-" + sc.getContext("/foo##1").getInitParameter("id"));
|
||||
pw.println("04-" + sc.getContext("/foo##2").getInitParameter("id"));
|
||||
pw.println("05-" + sc.getContext("/foo##3").getInitParameter("id"));
|
||||
pw.println("06-" + sc.getContext("/unknown").getInitParameter("id"));
|
||||
pw.println("07-" + sc.getContext("/").getInitParameter("id"));
|
||||
pw.println("08-" + sc.getContext("/foo/bar").getInitParameter("id"));
|
||||
pw.println("09-" + sc.getContext("/football").getInitParameter("id"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SetIdListener implements LifecycleListener {
|
||||
|
||||
private final String id;
|
||||
|
||||
public SetIdListener(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lifecycleEvent(LifecycleEvent event) {
|
||||
if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType())) {
|
||||
((Context) event.getSource()).getServletContext().setInitParameter("id", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The expectation is that you can set a context attribute on
|
||||
* ServletContextB from ServletContextA and then access that attribute via
|
||||
* a cross-context dispatch to ServletContextB.
|
||||
*/
|
||||
@Test
|
||||
public void testCrossContextSetAttribute() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx2 = tomcat.addContext("/second", null);
|
||||
GetAttributeServlet getAttributeServlet = new GetAttributeServlet();
|
||||
Tomcat.addServlet(ctx2, "getAttributeServlet", getAttributeServlet);
|
||||
ctx2.addServletMappingDecoded("/test", "getAttributeServlet");
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx1 = tomcat.addContext("/first", null);
|
||||
ctx1.setCrossContext(true);
|
||||
SetAttributeServlet setAttributeServlet = new SetAttributeServlet("/test", "/second");
|
||||
Tomcat.addServlet(ctx1, "setAttributeServlet", setAttributeServlet);
|
||||
ctx1.addServletMappingDecoded("/test", "setAttributeServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/first/test", bc, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("01-PASS", bc.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class SetAttributeServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String ATTRIBUTE_NAME = "setAttributeTest";
|
||||
private static final String ATTRIBUTE_VALUE = "abcde";
|
||||
|
||||
private final String targetContextPath;
|
||||
private final String targetPath;
|
||||
|
||||
public SetAttributeServlet(String targetPath, String targetContextPath) {
|
||||
this.targetPath = targetPath;
|
||||
this.targetContextPath = targetContextPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
ServletContext sc;
|
||||
if (targetContextPath == null) {
|
||||
sc = req.getServletContext();
|
||||
} else {
|
||||
sc = req.getServletContext().getContext(targetContextPath);
|
||||
}
|
||||
sc.setAttribute(ATTRIBUTE_NAME, ATTRIBUTE_VALUE);
|
||||
sc.getRequestDispatcher(targetPath).forward(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class GetAttributeServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
String value = (String) req.getServletContext().getAttribute(
|
||||
SetAttributeServlet.ATTRIBUTE_NAME);
|
||||
if (SetAttributeServlet.ATTRIBUTE_VALUE.equals(value)) {
|
||||
pw.print("01-PASS");
|
||||
} else {
|
||||
pw.print("01-FAIL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
@RunWith(value = Parameterized.class)
|
||||
public class TestApplicationContextStripPathParams extends TomcatBaseTest {
|
||||
|
||||
private final String input;
|
||||
private final String expectedOutput;
|
||||
|
||||
public TestApplicationContextStripPathParams(String input, String expectedOutput) {
|
||||
this.input = input;
|
||||
this.expectedOutput = expectedOutput;
|
||||
}
|
||||
|
||||
@Parameters(name = "{index}: input[{0}]")
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{ "/foo", "/foo"},
|
||||
{ "/foo/", "/foo/"},
|
||||
{ "/foo/bar", "/foo/bar"},
|
||||
{ "/foo;", "/foo"},
|
||||
{ "/foo;/", "/foo/"},
|
||||
{ "/foo;/bar", "/foo/bar"},
|
||||
{ "/foo;a=1", "/foo"},
|
||||
{ "/foo;a=1/", "/foo/"},
|
||||
{ "/foo;a=1/bar", "/foo/bar"},
|
||||
// Arguably not valid but does the right thing anyway
|
||||
{ ";/foo", "/foo"},
|
||||
{ ";a=1/foo", "/foo"},
|
||||
{ ";/foo/bar", "/foo/bar"},
|
||||
{ ";/foo;a=1/bar", "/foo/bar"},
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringPathParams() {
|
||||
String output = ApplicationContext.stripPathParams(input);
|
||||
Assert.assertEquals(expectedOutput, output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.filters.AddDefaultCharsetFilter;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.descriptor.web.FilterDef;
|
||||
import org.apache.tomcat.util.modeler.Registry;
|
||||
|
||||
public class TestApplicationFilterConfig extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testBug54170() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "HelloWorld", new HelloWorldServlet());
|
||||
ctx.addServletMappingDecoded("/", "HelloWorld");
|
||||
|
||||
// Add a filter with a name that should be escaped if used in a JMX
|
||||
// object name
|
||||
FilterDef filterDef = new FilterDef();
|
||||
filterDef.setFilterClass(AddDefaultCharsetFilter.class.getName());
|
||||
filterDef.setFilterName("bug54170*");
|
||||
ctx.addFilterDef(filterDef);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
final MBeanServer mbeanServer =
|
||||
Registry.getRegistry(null, null).getMBeanServer();
|
||||
|
||||
// There should be one Servlet MBean registered
|
||||
Set<ObjectName> servlets = mbeanServer.queryNames(
|
||||
new ObjectName("Tomcat:j2eeType=Servlet,*"), null);
|
||||
Assert.assertEquals(1, servlets.size());
|
||||
|
||||
// There should be one Filter MBean registered
|
||||
Set<ObjectName> filters = mbeanServer.queryNames(
|
||||
new ObjectName("Tomcat:j2eeType=Filter,*"), null);
|
||||
Assert.assertEquals(1, filters.size());
|
||||
}
|
||||
}
|
||||
357
test/org/apache/catalina/core/TestApplicationHttpRequest.java
Normal file
357
test/org/apache/catalina/core/TestApplicationHttpRequest.java
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestApplicationHttpRequest extends TomcatBaseTest {
|
||||
|
||||
/*
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=58836
|
||||
*/
|
||||
@Test
|
||||
public void testForwardQueryString01() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b" });
|
||||
doQueryStringTest(null, "a=b", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testForwardQueryString02() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "c" });
|
||||
doQueryStringTest(null, "a=b&a=c", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testForwardQueryString03() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b" });
|
||||
expected.put("c", new String[] { "d" });
|
||||
doQueryStringTest(null, "a=b&c=d", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testForwardQueryString04() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "e" });
|
||||
expected.put("c", new String[] { "d" });
|
||||
doQueryStringTest(null, "a=b&c=d&a=e", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testForwardQueryString05() throws Exception {
|
||||
// Parameters with no value are assigned a value of the empty string
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "e" });
|
||||
expected.put("c", new String[] { "" });
|
||||
doQueryStringTest(null, "a=b&c&a=e", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOriginalQueryString01() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b" });
|
||||
doQueryStringTest("a=b", null, expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOriginalQueryString02() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "c" });
|
||||
doQueryStringTest("a=b&a=c", null, expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOriginalQueryString03() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b" });
|
||||
expected.put("c", new String[] { "d" });
|
||||
doQueryStringTest("a=b&c=d", null, expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOriginalQueryString04() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "e" });
|
||||
expected.put("c", new String[] { "d" });
|
||||
doQueryStringTest("a=b&c=d&a=e", null, expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOriginalQueryString05() throws Exception {
|
||||
// Parameters with no value are assigned a value of the empty string
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "e" });
|
||||
expected.put("c", new String[] { "" });
|
||||
doQueryStringTest("a=b&c&a=e", null, expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergeQueryString01() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "z", "b" });
|
||||
doQueryStringTest("a=b", "a=z", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergeQueryString02() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "z", "b", "e" });
|
||||
expected.put("c", new String[] { "" });
|
||||
doQueryStringTest("a=b&c&a=e", "a=z", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergeQueryString03() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b", "e" });
|
||||
expected.put("c", new String[] { "z", "" });
|
||||
doQueryStringTest("a=b&c&a=e", "c=z", expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergeQueryString04() throws Exception {
|
||||
Map<String,String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "", "b", "e" });
|
||||
expected.put("c", new String[] { "" });
|
||||
doQueryStringTest("a=b&c&a=e", "a", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeQueryString05() throws Exception {
|
||||
// https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82
|
||||
// "Test" = "Test"
|
||||
String test = "\u0422\u0435\u0441\u0442";
|
||||
String query = test + "=%D0%A2%D0%B5%D1%81%D1%82";
|
||||
|
||||
Map<String, String[]> expected = new HashMap<>();
|
||||
expected.put("a", new String[] { "b" });
|
||||
expected.put(test, new String[] { test });
|
||||
doQueryStringTest("a=b", query, expected);
|
||||
}
|
||||
|
||||
|
||||
private void doQueryStringTest(String originalQueryString, String forwardQueryString,
|
||||
Map<String,String[]> expected) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
if (forwardQueryString == null) {
|
||||
Tomcat.addServlet(ctx, "forward", new ForwardServlet("/display"));
|
||||
} else {
|
||||
Tomcat.addServlet(ctx, "forward", new ForwardServlet("/display?" + forwardQueryString));
|
||||
}
|
||||
ctx.addServletMappingDecoded("/forward", "forward");
|
||||
|
||||
Tomcat.addServlet(ctx, "display", new DisplayParameterServlet(expected));
|
||||
ctx.addServletMappingDecoded("/display", "display");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk response = new ByteChunk();
|
||||
StringBuilder target = new StringBuilder("http://localhost:");
|
||||
target.append(getPort());
|
||||
target.append("/forward");
|
||||
if (originalQueryString != null) {
|
||||
target.append('?');
|
||||
target.append(originalQueryString);
|
||||
}
|
||||
int rc = getUrl(target.toString(), response, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", response.toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testParameterImmutability() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "forward", new ForwardServlet("/modify"));
|
||||
ctx.addServletMappingDecoded("/forward", "forward");
|
||||
|
||||
Tomcat.addServlet(ctx, "modify", new ModifyParameterServlet());
|
||||
ctx.addServletMappingDecoded("/modify", "modify");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk response = new ByteChunk();
|
||||
StringBuilder target = new StringBuilder("http://localhost:");
|
||||
target.append(getPort());
|
||||
target.append("/forward");
|
||||
int rc = getUrl(target.toString(), response, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", response.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class ForwardServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String target;
|
||||
|
||||
public ForwardServlet(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
req.getRequestDispatcher(target).forward(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DisplayParameterServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Map<String,String[]> expected;
|
||||
|
||||
public DisplayParameterServlet(Map<String,String[]> expected) {
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
PrintWriter w = resp.getWriter();
|
||||
Map<String,String[]> actual = req.getParameterMap();
|
||||
|
||||
boolean ok = true;
|
||||
for (Entry<String,String[]> entry : actual.entrySet()) {
|
||||
String[] expectedValue = expected.get(entry.getKey());
|
||||
if (expectedValue == null ||
|
||||
expectedValue.length != entry.getValue().length) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < expectedValue.length; i++) {
|
||||
if (!expectedValue[i].equals(entry.getValue()[i])) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
w.print("OK");
|
||||
return;
|
||||
}
|
||||
boolean firstParam = true;
|
||||
for (Entry<String,String[]> param : actual.entrySet()) {
|
||||
if (firstParam) {
|
||||
firstParam = false;
|
||||
} else {
|
||||
w.print(';');
|
||||
}
|
||||
w.print(param.getKey());
|
||||
w.print(':');
|
||||
boolean firstValue = true;
|
||||
for (String value : param.getValue()) {
|
||||
if (firstValue) {
|
||||
firstValue = false;
|
||||
} else {
|
||||
w.print(',');
|
||||
}
|
||||
w.print('(');
|
||||
w.print(value);
|
||||
w.print(')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ModifyParameterServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Suppress warnings generated because the code is trying to put the
|
||||
// wrong type of values into the Map
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
Map map = req.getParameterMap();
|
||||
|
||||
boolean insertWorks;
|
||||
try {
|
||||
map.put("test", new Integer[] { Integer.valueOf(0) });
|
||||
insertWorks = true;
|
||||
} catch (Throwable t) {
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
insertWorks = false;
|
||||
}
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
if (insertWorks) {
|
||||
pw.print("FAIL");
|
||||
} else {
|
||||
pw.print("OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
376
test/org/apache/catalina/core/TestApplicationMapping.java
Normal file
376
test/org/apache/catalina/core/TestApplicationMapping.java
Normal file
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
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.servlet4preview.http.HttpServletMapping;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestApplicationMapping extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingContextRoot() throws Exception {
|
||||
doTestMapping("/dummy", "", "", "", "CONTEXT_ROOT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingDefault() throws Exception {
|
||||
doTestMapping("/dummy", "/", "/foo", "", "DEFAULT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingExtension() throws Exception {
|
||||
doTestMapping("/dummy", "*.test", "/foo/bar.test", "foo/bar", "EXTENSION");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingExact() throws Exception {
|
||||
doTestMapping("/dummy", "/foo/bar", "/foo/bar", "foo/bar", "EXACT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingPathNone() throws Exception {
|
||||
doTestMapping("/dummy", "/foo/bar/*", "/foo/bar", null, "PATH");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingPathSeparatorOnly() throws Exception {
|
||||
doTestMapping("/dummy", "/foo/bar/*", "/foo/bar/", "", "PATH");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextNonRootMappingPath() throws Exception {
|
||||
doTestMapping("/dummy", "/foo/bar/*", "/foo/bar/foo2", "foo2", "PATH");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextRootMappingContextRoot() throws Exception {
|
||||
doTestMapping("", "", "", "", "CONTEXT_ROOT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextRootMappingDefault() throws Exception {
|
||||
doTestMapping("", "/", "/foo", "", "DEFAULT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextRootMappingExtension() throws Exception {
|
||||
doTestMapping("", "*.test", "/foo/bar.test", "foo/bar", "EXTENSION");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextRootMappingExact() throws Exception {
|
||||
doTestMapping("", "/foo/bar", "/foo/bar", "foo/bar", "EXACT");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextRootMappingPath() throws Exception {
|
||||
doTestMapping("", "/foo/bar/*", "/foo/bar/foo2", "foo2", "PATH");
|
||||
}
|
||||
|
||||
private void doTestMapping(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
doTestMappingDirect(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
tearDown();
|
||||
setUp();
|
||||
doTestMappingInclude(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
tearDown();
|
||||
setUp();
|
||||
doTestMappingNamedInclude(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
tearDown();
|
||||
setUp();
|
||||
doTestMappingForward(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
tearDown();
|
||||
setUp();
|
||||
doTestMappingNamedForward(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
tearDown();
|
||||
setUp();
|
||||
doTestMappingAsync(contextPath, mapping, requestPath, matchValue, matchType);
|
||||
}
|
||||
|
||||
private void doTestMappingDirect(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded(mapping, "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Mapping]"));
|
||||
}
|
||||
|
||||
private void doTestMappingInclude(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Tomcat.addServlet(ctx, "Include", new IncludeServlet());
|
||||
ctx.addServletMappingDecoded(mapping, "Include");
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded("/mapping", "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Include]"));
|
||||
|
||||
Assert.assertTrue(body, body.contains("IncludeMatchValue=[mapping]"));
|
||||
Assert.assertTrue(body, body.contains("IncludePattern=[/mapping]"));
|
||||
Assert.assertTrue(body, body.contains("IncludeMatchType=[EXACT]"));
|
||||
Assert.assertTrue(body, body.contains("IncludeServletName=[Mapping]"));
|
||||
}
|
||||
|
||||
private void doTestMappingNamedInclude(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Tomcat.addServlet(ctx, "Include", new NamedIncludeServlet());
|
||||
ctx.addServletMappingDecoded(mapping, "Include");
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded("/mapping", "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Include]"));
|
||||
}
|
||||
|
||||
private void doTestMappingForward(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Tomcat.addServlet(ctx, "Forward", new ForwardServlet());
|
||||
ctx.addServletMappingDecoded(mapping, "Forward");
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded("/mapping", "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[mapping]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[/mapping]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[EXACT]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Mapping]"));
|
||||
|
||||
Assert.assertTrue(body, body.contains("ForwardMatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("ForwardPattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("ForwardMatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("ForwardServletName=[Forward]"));
|
||||
}
|
||||
|
||||
private void doTestMappingNamedForward(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Tomcat.addServlet(ctx, "Forward", new NamedForwardServlet());
|
||||
ctx.addServletMappingDecoded(mapping, "Forward");
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded("/mapping", "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Forward]"));
|
||||
}
|
||||
|
||||
private void doTestMappingAsync(String contextPath, String mapping, String requestPath,
|
||||
String matchValue, String matchType) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext(contextPath, null);
|
||||
|
||||
Wrapper w = Tomcat.addServlet(ctx, "Async", new AsyncServlet());
|
||||
w.setAsyncSupported(true);
|
||||
ctx.addServletMappingDecoded(mapping, "Async");
|
||||
Tomcat.addServlet(ctx, "Mapping", new MappingServlet());
|
||||
ctx.addServletMappingDecoded("/mapping", "Mapping");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + requestPath);
|
||||
String body = bc.toString();
|
||||
|
||||
Assert.assertTrue(body, body.contains("MatchValue=[mapping]"));
|
||||
Assert.assertTrue(body, body.contains("Pattern=[/mapping]"));
|
||||
Assert.assertTrue(body, body.contains("MatchType=[EXACT]"));
|
||||
Assert.assertTrue(body, body.contains("ServletName=[Mapping]"));
|
||||
|
||||
Assert.assertTrue(body, body.contains("AsyncMatchValue=[" + matchValue + "]"));
|
||||
Assert.assertTrue(body, body.contains("AsyncPattern=[" + mapping + "]"));
|
||||
Assert.assertTrue(body, body.contains("AsyncMatchType=[" + matchType + "]"));
|
||||
Assert.assertTrue(body, body.contains("AsyncServletName=[Async]"));
|
||||
}
|
||||
|
||||
|
||||
private static class IncludeServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher rd = req.getRequestDispatcher("/mapping");
|
||||
rd.include(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NamedIncludeServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher rd = req.getServletContext().getNamedDispatcher("Mapping");
|
||||
rd.include(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NamedForwardServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher rd = req.getServletContext().getNamedDispatcher("Mapping");
|
||||
rd.forward(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ForwardServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher rd = req.getRequestDispatcher("/mapping");
|
||||
rd.forward(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AsyncServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
AsyncContext ac = req.startAsync();
|
||||
ac.dispatch("/mapping");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MappingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain;charset=UTF-8");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
HttpServletMapping mapping = ((org.apache.catalina.servlet4preview.http.HttpServletRequest)
|
||||
req).getHttpServletMapping();
|
||||
pw.println("MatchValue=[" + mapping.getMatchValue() + "]");
|
||||
pw.println("Pattern=[" + mapping.getPattern() + "]");
|
||||
pw.println("MatchType=[" + mapping.getMappingMatch() + "]");
|
||||
pw.println("ServletName=[" + mapping.getServletName() + "]");
|
||||
HttpServletMapping includeMapping = (HttpServletMapping) req.getAttribute(
|
||||
ApplicationDispatcher.INCLUDE_MAPPING);
|
||||
if (includeMapping != null) {
|
||||
pw.println("IncludeMatchValue=[" + includeMapping.getMatchValue() + "]");
|
||||
pw.println("IncludePattern=[" + includeMapping.getPattern() + "]");
|
||||
pw.println("IncludeMatchType=[" + includeMapping.getMappingMatch() + "]");
|
||||
pw.println("IncludeServletName=[" + includeMapping.getServletName() + "]");
|
||||
|
||||
}
|
||||
HttpServletMapping forwardMapping = (HttpServletMapping) req.getAttribute(
|
||||
ApplicationDispatcher.FORWARD_MAPPING);
|
||||
if (forwardMapping != null) {
|
||||
pw.println("ForwardMatchValue=[" + forwardMapping.getMatchValue() + "]");
|
||||
pw.println("ForwardPattern=[" + forwardMapping.getPattern() + "]");
|
||||
pw.println("ForwardMatchType=[" + forwardMapping.getMappingMatch() + "]");
|
||||
pw.println("ForwardServletName=[" + forwardMapping.getServletName() + "]");
|
||||
}
|
||||
HttpServletMapping asyncMapping = (HttpServletMapping) req.getAttribute(
|
||||
ApplicationDispatcher.ASYNC_MAPPING);
|
||||
if (asyncMapping != null) {
|
||||
pw.println("AsyncMatchValue=[" + asyncMapping.getMatchValue() + "]");
|
||||
pw.println("AsyncPattern=[" + asyncMapping.getPattern() + "]");
|
||||
pw.println("AsyncMatchType=[" + asyncMapping.getMappingMatch() + "]");
|
||||
pw.println("AsyncServletName=[" + asyncMapping.getServletName() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.catalina.core;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestApplicationPushBuilder {
|
||||
|
||||
@Test
|
||||
public void test01() {
|
||||
doTest("foo", StandardCharsets.UTF_8, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02() {
|
||||
doTest("/foo", StandardCharsets.UTF_8, "/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test03() {
|
||||
doTest("%20foo", StandardCharsets.UTF_8, " foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test04() {
|
||||
doTest("fo%20o", StandardCharsets.UTF_8, "fo o");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test05() {
|
||||
doTest("foo%20", StandardCharsets.UTF_8, "foo ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test06() {
|
||||
doTest("%21foo", StandardCharsets.UTF_8, "!foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test07() {
|
||||
doTest("fo%21o", StandardCharsets.UTF_8, "fo!o");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test08() {
|
||||
doTest("foo%21", StandardCharsets.UTF_8, "foo!");
|
||||
}
|
||||
|
||||
|
||||
private void doTest(String input, Charset charset, String expected) {
|
||||
String result = ApplicationPushBuilder.decode(input, charset);
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.LifecycleState;
|
||||
|
||||
public class TestApplicationSessionCookieConfig {
|
||||
private ApplicationSessionCookieConfig applicationSessionCookieConfig;
|
||||
private final CustomContext context = new CustomContext();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
applicationSessionCookieConfig = new ApplicationSessionCookieConfig(
|
||||
context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCommentInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setComment("test");
|
||||
Assert.assertTrue(applicationSessionCookieConfig.getComment().equals("test"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetCommentNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setComment("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetDomainInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setDomain("test");
|
||||
Assert.assertTrue(applicationSessionCookieConfig.getDomain().equals("test"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetDomainNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setDomain("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetHttpOnlyInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setHttpOnly(true);
|
||||
Assert.assertTrue(applicationSessionCookieConfig.isHttpOnly());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetHttpOnlyNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setHttpOnly(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetMaxAgeInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setMaxAge(1);
|
||||
Assert.assertTrue(applicationSessionCookieConfig.getMaxAge() == 1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetMaxAgeNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setMaxAge(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetNameInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setName("test");
|
||||
Assert.assertTrue(applicationSessionCookieConfig.getName().equals("test"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetNameNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setName("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPathInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setPath("test");
|
||||
Assert.assertTrue(applicationSessionCookieConfig.getPath().equals("test"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetPathNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setPath("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSecureInitPhase() {
|
||||
context.setState(LifecycleState.STARTING_PREP);
|
||||
applicationSessionCookieConfig.setSecure(true);
|
||||
Assert.assertTrue(applicationSessionCookieConfig.isSecure());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSetSecureNotInitPhase() {
|
||||
context.setState(LifecycleState.STARTED);
|
||||
applicationSessionCookieConfig.setSecure(true);
|
||||
}
|
||||
|
||||
private static class CustomContext extends StandardContext {
|
||||
private volatile LifecycleState state;
|
||||
|
||||
@Override
|
||||
public LifecycleState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setState(LifecycleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
3005
test/org/apache/catalina/core/TestAsyncContextImpl.java
Normal file
3005
test/org/apache/catalina/core/TestAsyncContextImpl.java
Normal file
File diff suppressed because it is too large
Load Diff
151
test/org/apache/catalina/core/TestAsyncContextImplDispatch.java
Normal file
151
test/org/apache/catalina/core/TestAsyncContextImplDispatch.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Written for the specific test case of async Servlet, dispatches to sync
|
||||
* Servlet that then tries to call startAsync() but covers all combinations
|
||||
* for completeness.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestAsyncContextImplDispatch extends TomcatBaseTest {
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: tgt-sup [{0}], dis-sup [{1}], dis-st [{2}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
for (Boolean targetAsyncSupported : booleans) {
|
||||
for (Boolean dispatchAsyncSupported : booleans) {
|
||||
for (Boolean dispatchAsyncStart : booleans) {
|
||||
Boolean allowed = Boolean.valueOf(!dispatchAsyncStart.booleanValue() ||
|
||||
targetAsyncSupported.booleanValue() && dispatchAsyncSupported.booleanValue() &&
|
||||
dispatchAsyncStart.booleanValue());
|
||||
|
||||
parameterSets.add(new Object[] { targetAsyncSupported, dispatchAsyncSupported, dispatchAsyncStart, allowed} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public boolean targetAsyncSupported;
|
||||
@Parameter(1)
|
||||
public boolean dispatchAsyncSupported;
|
||||
@Parameter(2)
|
||||
public boolean dispatchAsyncStart;
|
||||
@Parameter(3)
|
||||
public boolean allowed;
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendError() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Wrapper w1 = Tomcat.addServlet(ctx, "target", new TesterServlet());
|
||||
w1.setAsyncSupported(targetAsyncSupported);
|
||||
ctx.addServletMappingDecoded("/target", "target");
|
||||
|
||||
Wrapper w2 = Tomcat.addServlet(ctx, "dispatch", new TesterDispatchServlet(dispatchAsyncStart));
|
||||
w2.setAsyncSupported(dispatchAsyncSupported);
|
||||
ctx.addServletMappingDecoded("/dispatch", "dispatch");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc;
|
||||
|
||||
rc = getUrl("http://localhost:" + getPort() + "/target", bc, null, null);
|
||||
|
||||
String body = bc.toString();
|
||||
|
||||
if (allowed) {
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", body);
|
||||
} else {
|
||||
Assert.assertEquals(500, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TesterServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
req.getRequestDispatcher("/dispatch").forward(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TesterDispatchServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean start;
|
||||
|
||||
public TesterDispatchServlet(boolean start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (start) {
|
||||
AsyncContext ac = req.startAsync();
|
||||
ac.complete();
|
||||
}
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.getWriter().write("OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
378
test/org/apache/catalina/core/TestAsyncContextStateChanges.java
Normal file
378
test/org/apache/catalina/core/TestAsyncContextStateChanges.java
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
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.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.connector.TestCoyoteAdapter;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
/*
|
||||
* Derived from a test for https://bz.apache.org/bugzilla/show_bug.cgi?id=63816
|
||||
* Expanded to cover https://bz.apache.org/bugzilla/show_bug.cgi?id=63817 and
|
||||
* additional scenarios.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestAsyncContextStateChanges extends TomcatBaseTest {
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: end [{0}], timing [{1}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
for (AsyncEnd asyncEnd : AsyncEnd.values()) {
|
||||
for (EndTiming endTiming : EndTiming.values()) {
|
||||
parameterSets.add(new Object[] { asyncEnd, endTiming });
|
||||
}
|
||||
}
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public AsyncEnd asyncEnd;
|
||||
|
||||
@Parameter(1)
|
||||
public EndTiming endTiming;
|
||||
|
||||
private ServletRequest servletRequest = null;
|
||||
private AsyncContext asyncContext = null;
|
||||
private AtomicBoolean failed = new AtomicBoolean();
|
||||
private CountDownLatch servletLatch;
|
||||
private CountDownLatch threadLatch;
|
||||
private CountDownLatch closeLatch;
|
||||
private CountDownLatch endLatch;
|
||||
private boolean dispatch;
|
||||
|
||||
@Test
|
||||
public void testAsync() throws Exception {
|
||||
dispatch = false;
|
||||
servletRequest = null;
|
||||
asyncContext = null;
|
||||
|
||||
// Initialise tracking fields
|
||||
failed.set(true);
|
||||
servletLatch = new CountDownLatch(1);
|
||||
threadLatch = new CountDownLatch(1);
|
||||
closeLatch = new CountDownLatch(1);
|
||||
endLatch = new CountDownLatch(1);
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
AsyncServlet bug63816Servlet = new AsyncServlet();
|
||||
Wrapper wrapper = Tomcat.addServlet(ctx, "bug63816Servlet", bug63816Servlet);
|
||||
wrapper.setAsyncSupported(true);
|
||||
ctx.addServletMappingDecoded("/*", "bug63816Servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Client client = new Client();
|
||||
client.setPort(getPort());
|
||||
client.setRequest(new String[] { "GET / HTTP/1.1" + SimpleHttpClient.CRLF +
|
||||
"Host: localhost:" + SimpleHttpClient.CRLF +
|
||||
SimpleHttpClient.CRLF});
|
||||
client.connect();
|
||||
client.sendRequest();
|
||||
|
||||
// Wait for Servlet to start processing request
|
||||
servletLatch.await();
|
||||
|
||||
if (asyncEnd.isError()) {
|
||||
client.disconnect();
|
||||
closeLatch.countDown();
|
||||
try {
|
||||
endLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
} else {
|
||||
client.setUseContentLength(true);
|
||||
client.readResponse(true);
|
||||
}
|
||||
|
||||
Assert.assertFalse(failed.get());
|
||||
}
|
||||
|
||||
|
||||
private static final class Client extends SimpleHttpClient {
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class AsyncServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
servletLatch.countDown();
|
||||
|
||||
if (dispatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!asyncEnd.isError()) {
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentLength(2);
|
||||
resp.getWriter().print("OK");
|
||||
}
|
||||
|
||||
servletRequest = req;
|
||||
asyncContext = req.startAsync();
|
||||
asyncContext.addListener(new Listener());
|
||||
if (!asyncEnd.isError()) {
|
||||
// Use a short timeout so the test does not pause for too long
|
||||
// waiting for the timeout to be triggered.
|
||||
asyncContext.setTimeout(1000);
|
||||
}
|
||||
Thread t = new NonContainerThread();
|
||||
|
||||
switch (endTiming) {
|
||||
case INLINE: {
|
||||
t.run();
|
||||
break;
|
||||
}
|
||||
case THREAD_AFTER_EXIT: {
|
||||
t.start();
|
||||
threadLatch.countDown();
|
||||
break;
|
||||
}
|
||||
case THREAD_BEFORE_EXIT: {
|
||||
t.start();
|
||||
try {
|
||||
threadLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
endLatch.countDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class NonContainerThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (endTiming == EndTiming.THREAD_AFTER_EXIT) {
|
||||
try {
|
||||
threadLatch.await();
|
||||
/*
|
||||
* As much as I dislike it, I don't see any easy way around
|
||||
* this hack. The latch above is released as the Servlet
|
||||
* exits but we need to wait for the post processing to
|
||||
* complete for the test to work as intended. In real-world
|
||||
* applications this does mean that there is a real chance
|
||||
* of an ISE. We may need to increase this delay for some CI
|
||||
* systems.
|
||||
*/
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the error if necessary
|
||||
if (asyncEnd.isError()) {
|
||||
try {
|
||||
closeLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
ServletResponse resp = asyncContext.getResponse();
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
OutputStream os = resp.getOutputStream();
|
||||
resp.setContentType("text/plain");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
os.write(TestCoyoteAdapter.TEXT_8K.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
if (endTiming != EndTiming.THREAD_AFTER_EXIT) {
|
||||
try {
|
||||
switch (asyncEnd) {
|
||||
case COMPLETE:
|
||||
case ERROR_COMPLETE: {
|
||||
asyncContext.complete();
|
||||
break;
|
||||
}
|
||||
case DISPATCH:
|
||||
case ERROR_DISPATCH: {
|
||||
dispatch = true;
|
||||
asyncContext.dispatch();
|
||||
break;
|
||||
}
|
||||
case NONE:
|
||||
case ERROR_NONE: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The request must stay in async mode until doGet() exists
|
||||
if (servletRequest.isAsyncStarted()) {
|
||||
failed.set(false);
|
||||
}
|
||||
} finally {
|
||||
if (endTiming == EndTiming.THREAD_BEFORE_EXIT) {
|
||||
threadLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class Listener implements AsyncListener {
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException {
|
||||
if (endTiming == EndTiming.INLINE) {
|
||||
endLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException {
|
||||
// Need to handle timeouts for THREAD_AFTER_EXIT in the listener to
|
||||
// avoid concurrency issues.
|
||||
if (endTiming == EndTiming.THREAD_AFTER_EXIT) {
|
||||
switch (asyncEnd) {
|
||||
case COMPLETE: {
|
||||
asyncContext.complete();
|
||||
break;
|
||||
}
|
||||
case DISPATCH: {
|
||||
dispatch = true;
|
||||
asyncContext.dispatch();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
if (servletRequest.isAsyncStarted() == asyncEnd.isNone()) {
|
||||
failed.set(false);
|
||||
}
|
||||
endLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException {
|
||||
// Need to handle errors for THREAD_AFTER_EXIT in the listener to
|
||||
// avoid concurrency issues.
|
||||
if (endTiming == EndTiming.THREAD_AFTER_EXIT) {
|
||||
switch (asyncEnd) {
|
||||
case ERROR_COMPLETE: {
|
||||
asyncContext.complete();
|
||||
break;
|
||||
}
|
||||
case ERROR_DISPATCH: {
|
||||
dispatch = true;
|
||||
asyncContext.dispatch();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// NO-OP
|
||||
}
|
||||
if (servletRequest.isAsyncStarted() == asyncEnd.isNone()) {
|
||||
failed.set(false);
|
||||
}
|
||||
endLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum AsyncEnd {
|
||||
|
||||
NONE ( true, false),
|
||||
COMPLETE (false, false),
|
||||
DISPATCH (false, false),
|
||||
ERROR_NONE ( true, true),
|
||||
ERROR_COMPLETE(false, true),
|
||||
ERROR_DISPATCH(false, true);
|
||||
|
||||
final boolean none;
|
||||
final boolean error;
|
||||
|
||||
private AsyncEnd(boolean none, boolean error) {
|
||||
this.none = none;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public boolean isNone() {
|
||||
return none;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum EndTiming {
|
||||
INLINE,
|
||||
THREAD_BEFORE_EXIT,
|
||||
THREAD_AFTER_EXIT
|
||||
}
|
||||
}
|
||||
163
test/org/apache/catalina/core/TestDefaultInstanceManager.java
Normal file
163
test/org/apache/catalina/core/TestDefaultInstanceManager.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.jasper.servlet.JasperInitializer;
|
||||
import org.apache.tomcat.InstanceManager;
|
||||
|
||||
|
||||
public class TestDefaultInstanceManager extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testClassUnloading() throws Exception {
|
||||
|
||||
DefaultInstanceManager instanceManager = doClassUnloadingPrep();
|
||||
|
||||
// Request a JSP page (that doesn't load any tag libraries etc.)
|
||||
// This page does use @PostConstruct to ensure that the cache does not
|
||||
// retain strong references
|
||||
getUrl("http://localhost:" + getPort() + "/test/annotations.jsp");
|
||||
// Request a second JSP (again, no tag libraries etc.)
|
||||
getUrl("http://localhost:" + getPort() + "/test/bug36923.jsp");
|
||||
|
||||
// Check the number of classes in the cache
|
||||
int count = instanceManager.getAnnotationCacheSize();
|
||||
|
||||
// Request a third JSP (again, no tag libraries etc.)
|
||||
getUrl("http://localhost:" + getPort() + "/test/bug5nnnn/bug51544.jsp");
|
||||
|
||||
// Force a GC to clear out unloaded class (first JSP)
|
||||
System.gc();
|
||||
|
||||
// Spin a while until GC happens or we wait too long
|
||||
int loop = 0;
|
||||
while (loop < 10) {
|
||||
instanceManager.backgroundProcess();
|
||||
if (instanceManager.getAnnotationCacheSize() == count) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
loop++;
|
||||
}
|
||||
|
||||
// First JSP should be unloaded and replaced by third (second left
|
||||
// alone) so no change in overall count
|
||||
Assert.assertEquals(count, instanceManager.getAnnotationCacheSize());
|
||||
}
|
||||
|
||||
private DefaultInstanceManager doClassUnloadingPrep() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Create the context (don't use addWebapp as we want to modify the
|
||||
// JSP Servlet settings).
|
||||
File appDir = new File("test/webapp");
|
||||
StandardContext ctxt = (StandardContext) tomcat.addContext(
|
||||
null, "/test", appDir.getAbsolutePath());
|
||||
|
||||
ctxt.addServletContainerInitializer(new JasperInitializer(), null);
|
||||
|
||||
// Configure the defaults and then tweak the JSP servlet settings
|
||||
// Note: Min value for maxLoadedJsps is 2
|
||||
Tomcat.initWebappDefaults(ctxt);
|
||||
Wrapper w = (Wrapper) ctxt.findChild("jsp");
|
||||
w.addInitParameter("maxLoadedJsps", "2");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
return (DefaultInstanceManager) ctxt.getInstanceManager();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Performance test. Comment out @Ignore to run the test.
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void testConcurrency() throws Exception {
|
||||
// Create a populated InstanceManager
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context ctx = tomcat.addContext(null, "", null);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
InstanceManager im = ctx.getInstanceManager();
|
||||
|
||||
for (int i = 1; i < 9; i++) {
|
||||
doTestConcurrency(im, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doTestConcurrency(InstanceManager im, int threadCount) throws Exception {
|
||||
long start = System.nanoTime();
|
||||
|
||||
Thread[] threads = new Thread[threadCount];
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threads[i] = new Thread(new InstanceManagerRunnable(im));
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
|
||||
long duration = System.nanoTime() - start;
|
||||
|
||||
System.out.println(threadCount + " threads completed in " + duration + "ns");
|
||||
}
|
||||
|
||||
|
||||
private class InstanceManagerRunnable implements Runnable {
|
||||
|
||||
private final InstanceManager im;
|
||||
|
||||
private InstanceManagerRunnable(InstanceManager im) {
|
||||
this.im = im;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Object test = new DefaultServlet();
|
||||
for (int i = 0; i < 200000; i++) {
|
||||
im.newInstance(test);
|
||||
im.destroyInstance(test);
|
||||
}
|
||||
} catch (NamingException | IllegalAccessException | InvocationTargetException ne) {
|
||||
ne.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
184
test/org/apache/catalina/core/TestNamingContextListener.java
Normal file
184
test/org/apache/catalina/core/TestNamingContextListener.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.descriptor.web.ContextEnvironment;
|
||||
|
||||
public class TestNamingContextListener extends TomcatBaseTest {
|
||||
|
||||
private static final String BUG49132_NAME = "TestName";
|
||||
private static final String BUG49132_VALUE = "Test Value";
|
||||
|
||||
private static final String BUG54096_NameA = "envA";
|
||||
private static final String BUG54096_ValueA = "valueA";
|
||||
private static final String BUG54096_NameB = "envB";
|
||||
private static final String BUG54096_ValueB = "B";
|
||||
|
||||
/*
|
||||
* Test JNDI is available to ServletContextListeners.
|
||||
*/
|
||||
@Test
|
||||
public void testBug49132() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Enable JNDI - it is disabled by default
|
||||
tomcat.enableNaming();
|
||||
|
||||
ContextEnvironment environment = new ContextEnvironment();
|
||||
environment.setType(BUG49132_VALUE.getClass().getName());
|
||||
environment.setName(BUG49132_NAME);
|
||||
environment.setValue(BUG49132_VALUE);
|
||||
ctx.getNamingResources().addEnvironment(environment);
|
||||
|
||||
ctx.addApplicationListener(Bug49132Listener.class.getName());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
|
||||
}
|
||||
|
||||
public static final class Bug49132Listener implements ServletContextListener {
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
javax.naming.Context initCtx;
|
||||
try {
|
||||
initCtx = new InitialContext();
|
||||
javax.naming.Context envCtx =
|
||||
(javax.naming.Context) initCtx.lookup("java:comp/env");
|
||||
String value = (String) envCtx.lookup(BUG49132_NAME);
|
||||
if (!BUG49132_VALUE.equals(value)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
} catch (NamingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54096() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Enable JNDI - it is disabled by default
|
||||
tomcat.enableNaming();
|
||||
|
||||
ContextEnvironment environmentA = new ContextEnvironment();
|
||||
environmentA.setType(Bug54096EnvA.class.getName());
|
||||
environmentA.setName(BUG54096_NameA);
|
||||
environmentA.setValue(BUG54096_ValueA);
|
||||
ctx.getNamingResources().addEnvironment(environmentA);
|
||||
|
||||
ContextEnvironment environmentB = new ContextEnvironment();
|
||||
environmentB.setType(Bug54096EnvB.class.getName());
|
||||
environmentB.setName(BUG54096_NameB);
|
||||
environmentB.setValue(BUG54096_ValueB);
|
||||
ctx.getNamingResources().addEnvironment(environmentB);
|
||||
|
||||
ctx.addApplicationListener(Bug54096Listener.class.getName());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
|
||||
}
|
||||
|
||||
public static class Bug54096EnvA {
|
||||
|
||||
private final String value;
|
||||
|
||||
public Bug54096EnvA(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bug54096EnvB {
|
||||
|
||||
private final char value;
|
||||
|
||||
public Bug54096EnvB(char value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public char getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Bug54096Listener implements
|
||||
ServletContextListener {
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
javax.naming.Context initCtx;
|
||||
try {
|
||||
initCtx = new InitialContext();
|
||||
javax.naming.Context envCtx =
|
||||
(javax.naming.Context) initCtx.lookup("java:comp/env");
|
||||
|
||||
// Validate entry A
|
||||
Bug54096EnvA valueA =
|
||||
(Bug54096EnvA) envCtx.lookup(BUG54096_NameA);
|
||||
if (!BUG54096_ValueA.equals(valueA.getValue())) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
// Validate entry B
|
||||
Bug54096EnvB valueB =
|
||||
(Bug54096EnvB) envCtx.lookup(BUG54096_NameB);
|
||||
if (BUG54096_ValueB.charAt(0) != valueB.getValue()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
} catch (NamingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1056
test/org/apache/catalina/core/TestStandardContext.java
Normal file
1056
test/org/apache/catalina/core/TestStandardContext.java
Normal file
File diff suppressed because it is too large
Load Diff
118
test/org/apache/catalina/core/TestStandardContextAliases.java
Normal file
118
test/org/apache/catalina/core/TestStandardContextAliases.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
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.WebResourceRoot;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.catalina.webresources.StandardRoot;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestStandardContextAliases extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testDirContextAliases() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
File lib = new File("webapps/examples/WEB-INF/lib");
|
||||
ctx.setResources(new StandardRoot(ctx));
|
||||
ctx.getResources().createWebResourceSet(
|
||||
WebResourceRoot.ResourceSetType.POST, "/WEB-INF/lib",
|
||||
lib.getAbsolutePath(), null, "/");
|
||||
|
||||
|
||||
Tomcat.addServlet(ctx, "test", new TestServlet());
|
||||
ctx.addServletMappingDecoded("/", "test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + "/");
|
||||
|
||||
String result = res.toString();
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
|
||||
Assert.assertTrue(result.indexOf("00-PASS") > -1);
|
||||
Assert.assertTrue(result.indexOf("01-PASS") > -1);
|
||||
Assert.assertTrue(result.indexOf("02-PASS") > -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for the JSTL JARs in WEB-INF/lib.
|
||||
*/
|
||||
public static class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
ServletContext context = getServletContext();
|
||||
|
||||
// Check resources individually
|
||||
URL url = context.getResource("/WEB-INF/lib/taglibs-standard-spec-1.2.5.jar");
|
||||
if (url != null) {
|
||||
resp.getWriter().write("00-PASS\n");
|
||||
}
|
||||
|
||||
url = context.getResource("/WEB-INF/lib/taglibs-standard-impl-1.2.5.jar");
|
||||
if (url != null) {
|
||||
resp.getWriter().write("01-PASS\n");
|
||||
}
|
||||
|
||||
// Check a directory listing
|
||||
Set<String> libs = context.getResourcePaths("/WEB-INF/lib");
|
||||
if (libs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!libs.contains("/WEB-INF/lib/taglibs-standard-spec-1.2.5.jar")) {
|
||||
return;
|
||||
}
|
||||
if (!libs.contains("/WEB-INF/lib/taglibs-standard-impl-1.2.5.jar")) {
|
||||
return;
|
||||
}
|
||||
|
||||
resp.getWriter().write("02-PASS\n");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
282
test/org/apache/catalina/core/TestStandardContextResources.java
Normal file
282
test/org/apache/catalina/core/TestStandardContextResources.java
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
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.LifecycleListener;
|
||||
import org.apache.catalina.startup.Constants;
|
||||
import org.apache.catalina.startup.ContextConfig;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.catalina.util.IOTools;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.WebXml;
|
||||
|
||||
public class TestStandardContextResources extends TomcatBaseTest {
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// BZ 49218: The test fails if JreMemoryLeakPreventionListener is not
|
||||
// present. The listener affects the JVM, and thus not only the current,
|
||||
// but also the subsequent tests that are run in the same JVM. So it is
|
||||
// fair to add it in every test.
|
||||
tomcat.getServer().addLifecycleListener(
|
||||
new JreMemoryLeakPreventionListener());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResources() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp-fragments");
|
||||
// app dir is relative to server home
|
||||
Context ctx = tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
|
||||
|
||||
tomcat.start();
|
||||
|
||||
assertPageContains("/test/resourceA.jsp",
|
||||
"<p>resourceA.jsp in the web application</p>");
|
||||
assertPageContains("/test/resourceB.jsp",
|
||||
"<p>resourceB.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/folder/resourceC.jsp",
|
||||
"<p>resourceC.jsp in the web application</p>");
|
||||
assertPageContains("/test/folder/resourceD.jsp",
|
||||
"<p>resourceD.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/folder/resourceE.jsp",
|
||||
"<p>resourceE.jsp in the web application</p>");
|
||||
assertPageContains("/test/resourceG.jsp",
|
||||
"<p>resourceG.jsp in WEB-INF/classes</p>", 404);
|
||||
|
||||
// For BZ 54391. Relative ordering is specified in resources2.jar.
|
||||
// It is not absolute-ordering, so there may be other jars in the list
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> orderedLibs = (List<String>) ctx.getServletContext()
|
||||
.getAttribute(ServletContext.ORDERED_LIBS);
|
||||
if (orderedLibs.size() > 2) {
|
||||
log.warn("testResources(): orderedLibs: " + orderedLibs);
|
||||
}
|
||||
int index = orderedLibs.indexOf("resources.jar");
|
||||
int index2 = orderedLibs.indexOf("resources2.jar");
|
||||
Assert.assertTrue(orderedLibs.toString(), index >= 0 && index2 >= 0
|
||||
&& index < index2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourcesWebInfClasses() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// app dir is relative to server home
|
||||
File appDir = new File("test/webapp-fragments");
|
||||
|
||||
// Need to cast to be able to set StandardContext specific attribute
|
||||
StandardContext ctxt = (StandardContext)
|
||||
tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
|
||||
ctxt.setAddWebinfClassesResources(true);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
assertPageContains("/test/resourceA.jsp",
|
||||
"<p>resourceA.jsp in the web application</p>");
|
||||
assertPageContains("/test/resourceB.jsp",
|
||||
"<p>resourceB.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/folder/resourceC.jsp",
|
||||
"<p>resourceC.jsp in the web application</p>");
|
||||
assertPageContains("/test/folder/resourceD.jsp",
|
||||
"<p>resourceD.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/folder/resourceE.jsp",
|
||||
"<p>resourceE.jsp in the web application</p>");
|
||||
assertPageContains("/test/resourceG.jsp",
|
||||
"<p>resourceG.jsp in WEB-INF/classes</p>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourcesAbsoluteOrdering() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
File appDir = new File("test/webapp-fragments");
|
||||
|
||||
AbsoluteOrderContextConfig absoluteOrderConfig = new AbsoluteOrderContextConfig();
|
||||
|
||||
// app dir is relative to server home
|
||||
StandardContext ctx = (StandardContext) tomcat.addWebapp(null, "/test",
|
||||
appDir.getAbsolutePath(), (LifecycleListener) absoluteOrderConfig);
|
||||
|
||||
Tomcat.addServlet(ctx, "getresource", new GetResourceServlet());
|
||||
ctx.addServletMappingDecoded("/getresource", "getresource");
|
||||
|
||||
tomcat.start();
|
||||
assertPageContains("/test/getresource?path=/resourceF.jsp",
|
||||
"<p>resourceF.jsp in resources2.jar</p>");
|
||||
assertPageContains("/test/getresource?path=/resourceB.jsp",
|
||||
"<p>resourceB.jsp in resources.jar</p>");
|
||||
|
||||
// Check ordering, for BZ 54391
|
||||
Assert.assertEquals(Arrays.asList("resources.jar", "resources2.jar"), ctx
|
||||
.getServletContext().getAttribute(ServletContext.ORDERED_LIBS));
|
||||
|
||||
tomcat.getHost().removeChild(ctx);
|
||||
tomcat.getHost().stop();
|
||||
|
||||
// change ordering
|
||||
absoluteOrderConfig.swap();
|
||||
|
||||
ctx = (StandardContext) tomcat.addWebapp(null, "/test",
|
||||
appDir.getAbsolutePath(), (LifecycleListener) absoluteOrderConfig);
|
||||
Tomcat.addServlet(ctx, "getresource", new GetResourceServlet());
|
||||
ctx.addServletMappingDecoded("/getresource", "getresource");
|
||||
|
||||
tomcat.getHost().start();
|
||||
|
||||
assertPageContains("/test/getresource?path=/resourceF.jsp",
|
||||
"<p>resourceF.jsp in resources2.jar</p>");
|
||||
assertPageContains("/test/getresource?path=/resourceB.jsp",
|
||||
"<p>resourceB.jsp in resources2.jar</p>");
|
||||
|
||||
// Check ordering, for BZ 54391
|
||||
Assert.assertEquals(Arrays.asList("resources2.jar", "resources.jar"), ctx
|
||||
.getServletContext().getAttribute(ServletContext.ORDERED_LIBS));
|
||||
}
|
||||
|
||||
|
||||
public static class AbsoluteOrderContextConfig extends ContextConfig {
|
||||
|
||||
private boolean swap = false;
|
||||
|
||||
public AbsoluteOrderContextConfig() {
|
||||
super();
|
||||
// Prevent it from looking (if it finds one - it'll have dup error)
|
||||
setDefaultWebXml(Constants.NoDefaultWebXml);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebXml createWebXml() {
|
||||
WebXml wxml = new WebXml();
|
||||
if (swap) {
|
||||
wxml.addAbsoluteOrdering("resources2");
|
||||
wxml.addAbsoluteOrdering("resources");
|
||||
} else {
|
||||
wxml.addAbsoluteOrdering("resources");
|
||||
wxml.addAbsoluteOrdering("resources2");
|
||||
}
|
||||
return wxml;
|
||||
}
|
||||
|
||||
protected void swap() {
|
||||
swap = !swap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResources2() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp-fragments");
|
||||
// app dir is relative to server home
|
||||
StandardContext ctx = (StandardContext) tomcat.addWebapp(null, "/test",
|
||||
appDir.getAbsolutePath());
|
||||
skipTldsForResourceJars(ctx);
|
||||
|
||||
Tomcat.addServlet(ctx, "getresource", new GetResourceServlet());
|
||||
ctx.addServletMappingDecoded("/getresource", "getresource");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
assertPageContains("/test/getresource?path=/resourceF.jsp",
|
||||
"<p>resourceF.jsp in resources2.jar</p>");
|
||||
assertPageContains("/test/getresource?path=/resourceA.jsp",
|
||||
"<p>resourceA.jsp in the web application</p>");
|
||||
assertPageContains("/test/getresource?path=/resourceB.jsp",
|
||||
"<p>resourceB.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/getresource?path=/folder/resourceC.jsp",
|
||||
"<p>resourceC.jsp in the web application</p>");
|
||||
assertPageContains("/test/getresource?path=/folder/resourceD.jsp",
|
||||
"<p>resourceD.jsp in resources.jar</p>");
|
||||
assertPageContains("/test/getresource?path=/folder/resourceE.jsp",
|
||||
"<p>resourceE.jsp in the web application</p>");
|
||||
}
|
||||
|
||||
/**
|
||||
* A servlet that prints the requested resource. The path to the requested
|
||||
* resource is passed as a parameter, <code>path</code>.
|
||||
*/
|
||||
public static class GetResourceServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
ServletContext context = getServletContext();
|
||||
|
||||
// Check resources individually
|
||||
URL url = context.getResource(req.getParameter("path"));
|
||||
if (url == null) {
|
||||
resp.getWriter().println("Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream input = url.openStream();
|
||||
OutputStream output = resp.getOutputStream()) {
|
||||
IOTools.flow(input, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPageContains(String pageUrl, String expectedBody)
|
||||
throws IOException {
|
||||
|
||||
assertPageContains(pageUrl, expectedBody, 200);
|
||||
}
|
||||
|
||||
private void assertPageContains(String pageUrl, String expectedBody,
|
||||
int expectedStatus) throws IOException {
|
||||
ByteChunk res = new ByteChunk();
|
||||
int sc = getUrl("http://localhost:" + getPort() + pageUrl, res, null);
|
||||
|
||||
Assert.assertEquals(expectedStatus, sc);
|
||||
|
||||
if (expectedStatus == 200) {
|
||||
String result = res.toString();
|
||||
Assert.assertTrue(result, result.indexOf(expectedBody) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
185
test/org/apache/catalina/core/TestStandardContextValve.java
Normal file
185
test/org/apache/catalina/core/TestStandardContextValve.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequestEvent;
|
||||
import javax.servlet.ServletRequestListener;
|
||||
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.connector.Response;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.ErrorPage;
|
||||
|
||||
public class TestStandardContextValve extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testBug51653a() throws Exception {
|
||||
// Set up a container
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Traces order of events across multiple components
|
||||
StringBuilder trace = new StringBuilder();
|
||||
|
||||
//Add the error page
|
||||
Tomcat.addServlet(ctx, "errorPage", new Bug51653ErrorPage(trace));
|
||||
ctx.addServletMappingDecoded("/error", "errorPage");
|
||||
// And the handling for 404 responses
|
||||
ErrorPage errorPage = new ErrorPage();
|
||||
errorPage.setErrorCode(Response.SC_NOT_FOUND);
|
||||
errorPage.setLocation("/error");
|
||||
ctx.addErrorPage(errorPage);
|
||||
|
||||
// Add the request listener
|
||||
Bug51653RequestListener reqListener =
|
||||
new Bug51653RequestListener(trace);
|
||||
((StandardContext) ctx).addApplicationEventListener(reqListener);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
// Request a page that does not exist
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/invalid",
|
||||
new ByteChunk(), null);
|
||||
|
||||
// Need to allow time (but not too long in case the test fails) for
|
||||
// ServletRequestListener to complete
|
||||
int i = 20;
|
||||
while (i > 0) {
|
||||
if (trace.toString().endsWith("Destroy")) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(250);
|
||||
i--;
|
||||
}
|
||||
|
||||
Assert.assertEquals(Response.SC_NOT_FOUND, rc);
|
||||
Assert.assertEquals("InitErrorDestroy", trace.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug51653b() throws Exception {
|
||||
// Set up a container
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Traces order of events across multiple components
|
||||
StringBuilder trace = new StringBuilder();
|
||||
|
||||
// Add the page that generates the error
|
||||
Tomcat.addServlet(ctx, "test", new Bug51653ErrorTrigger());
|
||||
ctx.addServletMappingDecoded("/test", "test");
|
||||
|
||||
// Add the error page
|
||||
Tomcat.addServlet(ctx, "errorPage", new Bug51653ErrorPage(trace));
|
||||
ctx.addServletMappingDecoded("/error", "errorPage");
|
||||
// And the handling for 404 responses
|
||||
ErrorPage errorPage = new ErrorPage();
|
||||
errorPage.setErrorCode(Response.SC_NOT_FOUND);
|
||||
errorPage.setLocation("/error");
|
||||
ctx.addErrorPage(errorPage);
|
||||
|
||||
// Add the request listener
|
||||
Bug51653RequestListener reqListener =
|
||||
new Bug51653RequestListener(trace);
|
||||
((StandardContext) ctx).addApplicationEventListener(reqListener);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
// Request a page that does not exist
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/test",
|
||||
new ByteChunk(), null);
|
||||
|
||||
// Need to allow time (but not too long in case the test fails) for
|
||||
// ServletRequestListener to complete
|
||||
int i = 20;
|
||||
while (i > 0) {
|
||||
if (trace.toString().endsWith("Destroy")) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(250);
|
||||
i--;
|
||||
}
|
||||
|
||||
Assert.assertEquals(Response.SC_NOT_FOUND, rc);
|
||||
Assert.assertEquals("InitErrorDestroy", trace.toString());
|
||||
}
|
||||
|
||||
private static class Bug51653ErrorTrigger extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.sendError(Response.SC_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Bug51653ErrorPage extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
public Bug51653ErrorPage(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
sb.append("Error");
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().write("Error");
|
||||
}
|
||||
}
|
||||
|
||||
private static class Bug51653RequestListener
|
||||
implements ServletRequestListener {
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
public Bug51653RequestListener(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInitialized(ServletRequestEvent sre) {
|
||||
sb.append("Init");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestDestroyed(ServletRequestEvent sre) {
|
||||
sb.append("Destroy");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
169
test/org/apache/catalina/core/TestStandardHostValve.java
Normal file
169
test/org/apache/catalina/core/TestStandardHostValve.java
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequestEvent;
|
||||
import javax.servlet.ServletRequestListener;
|
||||
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.connector.Response;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.descriptor.web.ErrorPage;
|
||||
|
||||
public class TestStandardHostValve extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testErrorPageHandling() throws Exception {
|
||||
// Set up a container
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Add the error page
|
||||
Tomcat.addServlet(ctx, "error", new ErrorServlet());
|
||||
ctx.addServletMappingDecoded("/error", "error");
|
||||
|
||||
// Add the error handling page
|
||||
Tomcat.addServlet(ctx, "report", new ReportServlet());
|
||||
ctx.addServletMappingDecoded("/report/*", "report");
|
||||
|
||||
// And the handling for 500 responses
|
||||
ErrorPage errorPage500 = new ErrorPage();
|
||||
errorPage500.setErrorCode(Response.SC_INTERNAL_SERVER_ERROR);
|
||||
errorPage500.setLocation("/report/500");
|
||||
ctx.addErrorPage(errorPage500);
|
||||
|
||||
// And the default error handling
|
||||
ErrorPage errorPageDefault = new ErrorPage();
|
||||
errorPageDefault.setLocation("/report/default");
|
||||
ctx.addErrorPage(errorPageDefault);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
doTestErrorPageHandling(500, "/500");
|
||||
doTestErrorPageHandling(501, "/default");
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testInvalidErrorPage() throws Exception {
|
||||
// Set up a container
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Add a broken error page configuration
|
||||
ErrorPage errorPage500 = new ErrorPage();
|
||||
errorPage500.setErrorCode("java.lang.Exception");
|
||||
errorPage500.setLocation("/report/500");
|
||||
ctx.addErrorPage(errorPage500);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSRLAfterError() throws Exception {
|
||||
// Set up a container
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
// Add the error page
|
||||
Tomcat.addServlet(ctx, "error", new ErrorServlet());
|
||||
ctx.addServletMappingDecoded("/error", "error");
|
||||
|
||||
final List<String> result = new ArrayList<>();
|
||||
|
||||
// Add the request listener
|
||||
ServletRequestListener servletRequestListener = new ServletRequestListener() {
|
||||
|
||||
@Override
|
||||
public void requestDestroyed(ServletRequestEvent sre) {
|
||||
result.add("Visit requestDestroyed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInitialized(ServletRequestEvent sre) {
|
||||
result.add("Visit requestInitialized");
|
||||
}
|
||||
|
||||
};
|
||||
((StandardContext) ctx).addApplicationEventListener(servletRequestListener);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
// Request a page that triggers an error
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/error?errorCode=400", bc, null);
|
||||
|
||||
Assert.assertEquals(400, rc);
|
||||
Assert.assertTrue(result.contains("Visit requestInitialized"));
|
||||
Assert.assertTrue(result.contains("Visit requestDestroyed"));
|
||||
}
|
||||
|
||||
private void doTestErrorPageHandling(int error, String report)
|
||||
throws Exception {
|
||||
|
||||
// Request a page that triggers an error
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() +
|
||||
"/error?errorCode=" + error, bc, null);
|
||||
|
||||
Assert.assertEquals(error, rc);
|
||||
Assert.assertEquals(report, bc.toString());
|
||||
}
|
||||
|
||||
private static class ErrorServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
int error = Integer.parseInt(req.getParameter("errorCode"));
|
||||
resp.sendError(error);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReportServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
String pathInfo = req.getPathInfo();
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().print(pathInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user