blob: f65562e0192c1425c33c0d95e4df9c13f7b02486 [file] [log] [blame]
/*
* 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.startup;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerEvent;
import org.apache.catalina.ContainerListener;
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.LifecycleState;
import org.apache.catalina.Manager;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Session;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.util.IOTools;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.apache.tomcat.util.scan.StandardJarScanner;
/**
* Base test case that provides a Tomcat instance for each test - mainly so we
* don't have to keep writing the cleanup code.
*/
public abstract class TomcatBaseTest extends LoggingBaseTest {
// Used by parameterized tests. Defined here to reduce duplication.
protected static final Boolean[] booleans = new Boolean[] { Boolean.FALSE, Boolean.TRUE };
protected static final int DEFAULT_CLIENT_TIMEOUT_MS = 300_000;
public static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
private Tomcat tomcat;
private boolean accessLogEnabled = false;
/**
* Make the Tomcat instance available to sub-classes.
*
* @return A Tomcat instance without any pre-configured web applications
*/
public Tomcat getTomcatInstance() {
return tomcat;
}
/**
* Make the Tomcat instance preconfigured with test/webapp available to
* sub-classes.
* @param addJstl Should JSTL support be added to the test webapp
* @param start Should the Tomcat instance be started
*
* @return A Tomcat instance pre-configured with the web application located
* at test/webapp
*
* @throws LifecycleException If a problem occurs while starting the
* instance
*/
public Tomcat getTomcatInstanceTestWebapp(boolean addJstl, boolean start)
throws LifecycleException {
File appDir = new File("test/webapp");
Context ctx = tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
StandardJarScanner scanner = (StandardJarScanner) ctx.getJarScanner();
StandardJarScanFilter filter = (StandardJarScanFilter) scanner.getJarScanFilter();
filter.setTldSkip(filter.getTldSkip() + ",testclasses");
filter.setPluggabilitySkip(filter.getPluggabilitySkip() + ",testclasses");
if (addJstl) {
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, "/");
}
if (start) {
tomcat.start();
}
return tomcat;
}
public Context getProgrammaticRootContext() {
// No file system docBase required
Context ctx = tomcat.addContext("", null);
// Disable class path scanning - it slows the tests down by almost an order of magnitude
((StandardJarScanner) ctx.getJarScanner()).setScanClassPath(false);
return ctx;
}
public Context getProgrammaticRootContextWithManager() {
Context ctx = getProgrammaticRootContext();
if (ctx.getManager() == null) {
ctx.setManager(new StandardManager());
}
return ctx;
}
/*
* Sub-classes need to know port so they can connect
*/
public int getPort() {
return tomcat.getConnector().getLocalPort();
}
/*
* Sub-classes may want to check, whether an AccessLogValve is active
*/
public boolean isAccessLogEnabled() {
return accessLogEnabled;
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
// Trigger loading of catalina.properties
CatalinaProperties.getProperty("foo");
File appBase = new File(getTemporaryDirectory(), "webapps");
if (!appBase.exists() && !appBase.mkdir()) {
Assert.fail("Unable to create appBase for test");
}
tomcat = new TomcatWithFastSessionIDs();
String protocol = getProtocol();
Connector connector = new Connector(protocol);
// Listen only on localhost
Assert.assertTrue(connector.setProperty("address", InetAddress.getByName("localhost").getHostAddress()));
// Use random free port
connector.setPort(0);
// By default, a connector failure means a failed test
connector.setThrowOnFailure(true);
// Mainly set to reduce timeouts during async tests
Assert.assertTrue(connector.setProperty("connectionTimeout", "3000"));
tomcat.getService().addConnector(connector);
tomcat.setConnector(connector);
File catalinaBase = getTemporaryDirectory();
tomcat.setBaseDir(catalinaBase.getAbsolutePath());
tomcat.getHost().setAppBase(appBase.getAbsolutePath());
accessLogEnabled = Boolean.getBoolean("tomcat.test.accesslog");
if (accessLogEnabled) {
String accessLogDirectory = System
.getProperty("tomcat.test.reports");
if (accessLogDirectory == null) {
accessLogDirectory = new File(getBuildDirectory(), "logs")
.toString();
}
AccessLogValve alv = new AccessLogValve();
alv.setDirectory(accessLogDirectory);
alv.setPattern("%h %l %u %t \"%r\" %s %b %I %D");
tomcat.getHost().getPipeline().addValve(alv);
}
// Cannot delete the whole tempDir, because logs are there,
// but delete known subdirectories of it.
addDeleteOnTearDown(new File(catalinaBase, "webapps"));
addDeleteOnTearDown(new File(catalinaBase, "work"));
}
protected String getProtocol() {
// Has a protocol been specified
String protocol = System.getProperty("tomcat.test.protocol");
// Use NIO by default starting with Tomcat 8
if (protocol == null) {
protocol = Http11NioProtocol.class.getName();
}
return protocol;
}
@After
@Override
public void tearDown() throws Exception {
try {
// Some tests may call tomcat.destroy(), some tests may just call
// tomcat.stop(), some not call either method. Make sure that stop()
// & destroy() are called as necessary.
if (tomcat.server != null
&& tomcat.server.getState() != LifecycleState.DESTROYED) {
if (tomcat.server.getState() != LifecycleState.STOPPED) {
tomcat.stop();
}
tomcat.destroy();
}
} finally {
super.tearDown();
}
}
/**
* Simple Hello World servlet for use by test cases
*/
public static final class HelloWorldServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public static final String RESPONSE_TEXT =
"<html><body><p>Hello World</p></body></html>";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.print(RESPONSE_TEXT);
}
}
public static final class RequestDescriptor {
private final Map<String, String> requestInfo = new HashMap<>();
private final Map<String, String> contextInitParameters = new HashMap<>();
private final Map<String, String> contextAttributes = new HashMap<>();
private final Map<String, String> headers = new CaseInsensitiveKeyMap<>();
private final Map<String, String> attributes = new HashMap<>();
private final Map<String, String> params = new HashMap<>();
private final Map<String, String> sessionAttributes = new HashMap<>();
public Map<String, String> getRequestInfo() {
return requestInfo;
}
public Map<String, String> getContextInitParameters() {
return contextInitParameters;
}
public Map<String, String> getContextAttributes() {
return contextAttributes;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String> getAttributes() {
return attributes;
}
public Map<String, String> getParams() {
return params;
}
public Map<String, String> getSessionAttributes() {
return sessionAttributes;
}
public String getRequestInfo(String name) {
return requestInfo.get(name);
}
public void putRequestInfo(String name, String value) {
requestInfo.put(name, value);
}
public String getContextInitParameter(String name) {
return contextInitParameters.get(name);
}
public void putContextInitParameter(String name, String value) {
contextInitParameters.put(name, value);
}
public String getContextAttribute(String name) {
return contextAttributes.get(name);
}
public void putContextAttribute(String name, String value) {
contextAttributes.put(name, value);
}
public String getHeader(String name) {
return headers.get(name);
}
public void putHeader(String name, String value) {
headers.put(name, value);
}
public String getAttribute(String name) {
return attributes.get(name);
}
public void putAttribute(String name, String value) {
attributes.put(name, value);
}
public String getParam(String name) {
return params.get(name);
}
public void putParam(String name, String value) {
params.put(name, value);
}
public String getSessionAttribute(String name) {
return sessionAttributes.get(name);
}
public void putSessionAttribute(String name, String value) {
sessionAttributes.put(name, value);
}
public void compare (RequestDescriptor request) {
Map<String, String> base;
Map<String, String> cmp;
base = request.getRequestInfo();
cmp = this.getRequestInfo();
for (String name: base.keySet()) {
Assert.assertEquals("Request info " + name, base.get(name), cmp.get(name));
}
base = request.getContextInitParameters();
cmp = this.getContextInitParameters();
for (String name: base.keySet()) {
Assert.assertEquals("Context parameter " + name, base.get(name), cmp.get(name));
}
base = request.getContextAttributes();
cmp = this.getContextAttributes();
for (String name: base.keySet()) {
Assert.assertEquals("Context attribute " + name, base.get(name), cmp.get(name));
}
base = request.getHeaders();
cmp = this.getHeaders();
for (String name: base.keySet()) {
Assert.assertEquals("Header " + name, base.get(name), cmp.get(name));
}
base = request.getAttributes();
cmp = this.getAttributes();
for (String name: base.keySet()) {
Assert.assertEquals("Attribute " + name, base.get(name), cmp.get(name));
}
base = request.getParams();
cmp = this.getParams();
for (String name: base.keySet()) {
Assert.assertEquals("Param " + name, base.get(name), cmp.get(name));
}
base = request.getSessionAttributes();
cmp = this.getSessionAttributes();
for (String name: base.keySet()) {
Assert.assertEquals("Session attribute " + name, base.get(name), cmp.get(name));
}
}
}
public static final class SnoopResult {
public static RequestDescriptor parse(String body) {
int n;
int m;
String key;
String value;
String name;
RequestDescriptor request = new RequestDescriptor();
for (String line: body.split(System.lineSeparator())) {
n = line.indexOf(": ");
if (n > 0) {
key = line.substring(0, n);
value = line.substring(n + 2);
m = key.indexOf(':');
if (m > 0) {
name = key.substring(m + 1);
key = key.substring(0, m);
if (key.equals("CONTEXT-PARAM")) {
request.putContextInitParameter(name, value);
} else if (key.equals("CONTEXT-ATTRIBUTE")) {
request.putContextAttribute(name, value);
} else if (key.equals("HEADER")) {
request.putHeader(name, value);
} else if (key.equals("ATTRIBUTE")) {
request.putAttribute(name, value);
} else if (key.equals("PARAM")) {
request.putParam(name, value);
} else if (key.equals("SESSION-ATTRIBUTE")) {
request.putSessionAttribute(name, value);
} else {
request.putRequestInfo(key + ":" + name, value);
}
} else {
request.putRequestInfo(key, value);
}
}
}
return request;
}
}
/**
* Simple servlet that dumps request information. Tests using this should
* note that additional information may be added to in the future and should
* therefore test return values using SnoopResult.
*/
public static final class SnoopServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String name;
StringBuilder value;
Object attribute;
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
ServletContext ctx = this.getServletContext();
HttpSession session = request.getSession(false);
PrintWriter out = response.getWriter();
out.println("CONTEXT-NAME: " + ctx.getServletContextName());
out.println("CONTEXT-PATH: " + ctx.getContextPath());
out.println("CONTEXT-MAJOR-VERSION: " + ctx.getMajorVersion());
out.println("CONTEXT-MINOR-VERSION: " + ctx.getMinorVersion());
out.println("CONTEXT-SERVER-INFO: " + ctx.getServerInfo());
for (Enumeration<String> e = ctx.getInitParameterNames();
e.hasMoreElements();) {
name = e.nextElement();
out.println("CONTEXT-INIT-PARAM:" + name + ": " +
ctx.getInitParameter(name));
}
for (Enumeration<String> e = ctx.getAttributeNames();
e.hasMoreElements();) {
name = e.nextElement();
out.println("CONTEXT-ATTRIBUTE:" + name + ": " +
ctx.getAttribute(name));
}
out.println("REQUEST-CONTEXT-PATH: " + request.getContextPath());
out.println("REQUEST-SERVER-NAME: " + request.getServerName());
out.println("REQUEST-SERVER-PORT: " + request.getServerPort());
out.println("REQUEST-LOCAL-NAME: " + request.getLocalName());
out.println("REQUEST-LOCAL-ADDR: " + request.getLocalAddr());
out.println("REQUEST-LOCAL-PORT: " + request.getLocalPort());
out.println("REQUEST-REMOTE-HOST: " + request.getRemoteHost());
out.println("REQUEST-REMOTE-ADDR: " + request.getRemoteAddr());
out.println("REQUEST-REMOTE-PORT: " + request.getRemotePort());
out.println("REQUEST-PROTOCOL: " + request.getProtocol());
out.println("REQUEST-SCHEME: " + request.getScheme());
out.println("REQUEST-IS-SECURE: " + request.isSecure());
out.println("REQUEST-URI: " + request.getRequestURI());
out.println("REQUEST-URL: " + request.getRequestURL());
out.println("REQUEST-SERVLET-PATH: " + request.getServletPath());
out.println("REQUEST-METHOD: " + request.getMethod());
out.println("REQUEST-PATH-INFO: " + request.getPathInfo());
out.println("REQUEST-PATH-TRANSLATED: " +
request.getPathTranslated());
out.println("REQUEST-QUERY-STRING: " + request.getQueryString());
out.println("REQUEST-REMOTE-USER: " + request.getRemoteUser());
out.println("REQUEST-AUTH-TYPE: " + request.getAuthType());
out.println("REQUEST-USER-PRINCIPAL: " +
request.getUserPrincipal());
out.println("REQUEST-CHARACTER-ENCODING: " +
request.getCharacterEncoding());
out.println("REQUEST-CONTENT-LENGTH: " +
request.getContentLengthLong());
out.println("REQUEST-CONTENT-TYPE: " + request.getContentType());
out.println("REQUEST-LOCALE: " + request.getLocale());
for (Enumeration<String> e = request.getHeaderNames();
e.hasMoreElements();) {
name = e.nextElement();
value = new StringBuilder();
for (Enumeration<String> h = request.getHeaders(name);
h.hasMoreElements();) {
value.append(h.nextElement());
if (h.hasMoreElements()) {
value.append(';');
}
}
out.println("HEADER:" + name + ": " + value);
}
for (Enumeration<String> e = request.getAttributeNames();
e.hasMoreElements();) {
name = e.nextElement();
attribute = request.getAttribute(name);
out.println("ATTRIBUTE:" + name + ": " +
(attribute != null ? attribute : "(null)"));
}
for (Enumeration<String> e = request.getParameterNames();
e.hasMoreElements();) {
name = e.nextElement();
value = new StringBuilder();
String values[] = request.getParameterValues(name);
int m = values.length;
for (int j = 0; j < m; j++) {
value.append(values[j]);
if (j < m - 1) {
value.append(';');
}
}
out.println("PARAM:" + name + ": " + value);
}
out.println("SESSION-REQUESTED-ID: " +
request.getRequestedSessionId());
out.println("SESSION-REQUESTED-ID-COOKIE: " +
request.isRequestedSessionIdFromCookie());
out.println("SESSION-REQUESTED-ID-URL: " +
request.isRequestedSessionIdFromURL());
out.println("SESSION-REQUESTED-ID-VALID: " +
request.isRequestedSessionIdValid());
if (session != null) {
out.println("SESSION-ID: " + session.getId());
out.println("SESSION-CREATION-TIME: " +
session.getCreationTime());
out.println("SESSION-LAST-ACCESSED-TIME: " +
session.getLastAccessedTime());
out.println("SESSION-MAX-INACTIVE-INTERVAL: " +
session.getMaxInactiveInterval());
out.println("SESSION-IS-NEW: " + session.isNew());
for (Enumeration<String> e = session.getAttributeNames();
e.hasMoreElements();) {
name = e.nextElement();
attribute = session.getAttribute(name);
out.println("SESSION-ATTRIBUTE:" + name + ": " +
(attribute != null ? attribute : "(null)"));
}
}
int bodySize = 0;
if ("PUT".equals(request.getMethod())) {
InputStream is = request.getInputStream();
int read = 0;
byte[] buffer = new byte[8192];
while (read != -1) {
read = is.read(buffer);
if (read > -1) {
bodySize += read;
}
}
}
out.println("REQUEST-BODY-SIZE: " + bodySize);
}
}
/**
* Servlet that simply echos the request body back as the response body.
*/
public static class EchoBodyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// NO-OP - No body to echo
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Beware of clients that try to send the whole request body before
// reading any of the response. They may cause this test to lock up.
try (InputStream is = req.getInputStream();
OutputStream os = resp.getOutputStream()) {
IOTools.flow(is, os);
}
}
}
/*
* Wrapper for getting the response.
*/
public static ByteChunk getUrl(String path) throws IOException {
ByteChunk out = new ByteChunk();
getUrl(path, out, null);
return out;
}
public static int getUrl(String path, ByteChunk out, Map<String, List<String>> resHead)
throws IOException {
return getUrl(path, out, null, resHead);
}
public static int getUrl(String path, ByteChunk out, boolean followRedirects)
throws IOException {
return methodUrl(path, out, DEFAULT_CLIENT_TIMEOUT_MS, null, null, "GET", followRedirects);
}
public static int headUrl(String path, ByteChunk out, Map<String, List<String>> resHead)
throws IOException {
return methodUrl(path, out, DEFAULT_CLIENT_TIMEOUT_MS, null, resHead, "HEAD");
}
public static int getUrl(String path, ByteChunk out, Map<String, List<String>> reqHead,
Map<String, List<String>> resHead) throws IOException {
return getUrl(path, out, DEFAULT_CLIENT_TIMEOUT_MS, reqHead, resHead);
}
public static int getUrl(String path, ByteChunk out, int readTimeout,
Map<String, List<String>> reqHead, Map<String, List<String>> resHead)
throws IOException {
return methodUrl(path, out, readTimeout, reqHead, resHead, "GET");
}
public static int methodUrl(String path, ByteChunk out, int readTimeout,
Map<String, List<String>> reqHead, Map<String, List<String>> resHead, String method)
throws IOException {
return methodUrl(path, out, readTimeout, reqHead, resHead, method, true);
}
public static int methodUrl(String path, ByteChunk out, int readTimeout,
Map<String, List<String>> reqHead, Map<String, List<String>> resHead, String method,
boolean followRedirects) throws IOException {
URL url = URI.create(path).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setUseCaches(false);
connection.setReadTimeout(readTimeout);
connection.setRequestMethod(method);
connection.setInstanceFollowRedirects(followRedirects);
if (reqHead != null) {
for (Map.Entry<String, List<String>> entry : reqHead.entrySet()) {
StringBuilder valueList = new StringBuilder();
for (String value : entry.getValue()) {
if (valueList.length() > 0) {
valueList.append(',');
}
valueList.append(value);
}
connection.setRequestProperty(entry.getKey(),
valueList.toString());
}
}
connection.connect();
int rc = connection.getResponseCode();
if (resHead != null) {
// Skip the entry with null key that is used for the response line
// that some Map implementations may not accept.
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
if (entry.getKey() != null) {
resHead.put(entry.getKey(), entry.getValue());
}
}
}
InputStream is;
if (rc < 400) {
is = connection.getInputStream();
} else {
is = connection.getErrorStream();
}
if (is != null) {
try (BufferedInputStream bis = new BufferedInputStream(is)) {
byte[] buf = new byte[2048];
int rd = 0;
while((rd = bis.read(buf)) > 0) {
out.append(buf, 0, rd);
}
}
}
return rc;
}
public static ByteChunk postUrl(byte[] body, String path)
throws IOException {
ByteChunk out = new ByteChunk();
postUrl(body, path, out, null);
return out;
}
public static int postUrl(byte[] body, String path, ByteChunk out,
Map<String, List<String>> resHead) throws IOException {
return postUrl(body, path, out, null, resHead);
}
public static int postUrl(final byte[] body, String path, ByteChunk out,
Map<String, List<String>> reqHead,
Map<String, List<String>> resHead) throws IOException {
BytesStreamer s = new BytesStreamer() {
boolean done = false;
@Override
public byte[] next() {
done = true;
return body;
}
@Override
public int getLength() {
return body!=null?body.length:0;
}
@Override
public int available() {
if (done) {
return 0;
} else {
return getLength();
}
}
};
return postUrl(false,s,path,out,reqHead,resHead);
}
public static int postUrl(boolean stream, BytesStreamer streamer, String path, ByteChunk out,
Map<String, List<String>> reqHead,
Map<String, List<String>> resHead) throws IOException {
URL url = URI.create(path).toURL();
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setReadTimeout(1000000);
if (reqHead != null) {
for (Map.Entry<String, List<String>> entry : reqHead.entrySet()) {
StringBuilder valueList = new StringBuilder();
for (String value : entry.getValue()) {
if (valueList.length() > 0) {
valueList.append(',');
}
valueList.append(value);
}
connection.setRequestProperty(entry.getKey(),
valueList.toString());
}
}
if (streamer != null && stream) {
if (streamer.getLength()>0) {
connection.setFixedLengthStreamingMode(streamer.getLength());
} else {
connection.setChunkedStreamingMode(1024);
}
}
connection.connect();
// Write the request body
try (OutputStream os = connection.getOutputStream()) {
while (streamer != null && streamer.available() > 0) {
byte[] next = streamer.next();
os.write(next);
os.flush();
}
}
int rc = connection.getResponseCode();
if (resHead != null) {
Map<String, List<String>> head = connection.getHeaderFields();
resHead.putAll(head);
}
InputStream is;
if (rc < 400) {
is = connection.getInputStream();
} else {
is = connection.getErrorStream();
}
try (BufferedInputStream bis = new BufferedInputStream(is)) {
byte[] buf = new byte[2048];
int rd = 0;
while((rd = bis.read(buf)) > 0) {
out.append(buf, 0, rd);
}
}
return rc;
}
protected static String getStatusCode(String statusLine) {
if (statusLine == null || statusLine.length() < 12) {
return statusLine;
} else {
return statusLine.substring(9, 12);
}
}
protected static String getSingleHeader(String header, Map<String,List<String>> headers) {
// Assume headers is never null
// Assume that either:
// a) is correct since HTTP headers are case insensitive but most Map
// implementations are case-sensitive; or
// b) CaseInsensitiveKeyMap or similar is used
List<String> headerValues = headers.get(header);
// Looking for a single header. No matches are OK
if (headerValues == null) {
return null;
}
// Found a single header - return the header value
if (headerValues.size() == 1) {
return headerValues.get(0);
}
// More than one header value is an error
throw new IllegalStateException("Found multiple headers for [" + header + "]");
}
private static class TomcatWithFastSessionIDs extends Tomcat {
@Override
public void start() throws LifecycleException {
// Use fast, insecure session ID generation for all tests
Server server = getServer();
for (Service service : server.findServices()) {
Container e = service.getContainer();
for (Container h : e.findChildren()) {
for (Container c : h.findChildren()) {
Manager m = ((Context) c).getManager();
if (m == null) {
m = new StandardManager();
((Context) c).setManager(m);
}
if (m instanceof ManagerBase) {
((ManagerBase) m).setSecureRandomClass(
"org.apache.catalina.startup.FastNonSecureRandom");
}
}
}
}
super.start();
}
}
public static void recursiveCopy(final Path src, final Path dest)
throws IOException {
Files.walkFileTree(src, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Files.copy(dir, dest.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
Path destPath = dest.resolve(src.relativize(file));
Files.copy(file, destPath);
// Make sure that HostConfig thinks all newly copied files have
// been modified.
Assert.assertTrue("Failed to set last modified for [" + destPath + "]",
destPath.toFile().setLastModified(
System.currentTimeMillis() - 2 * HostConfig.FILE_MODIFICATION_RESOLUTION_MS));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException ioe)
throws IOException {
throw ioe;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException ioe)
throws IOException {
// NO-OP
return FileVisitResult.CONTINUE;
}});
}
public static void skipTldsForResourceJars(Context context) {
StandardJarScanner scanner = (StandardJarScanner) context.getJarScanner();
StandardJarScanFilter filter = (StandardJarScanFilter) scanner.getJarScanFilter();
filter.setTldSkip(filter.getTldSkip() + ",resources*.jar");
}
public static void forceSessionMaxInactiveInterval(Context context, int newIntervalSecs) {
Session[] sessions = context.getManager().findSessions();
for (Session session : sessions) {
session.setMaxInactiveInterval(newIntervalSecs);
}
}
/**
* Captures logs for the given logger names in the current ClassLoader.
*/
public static class LogCapture implements AutoCloseable {
protected final Level level;
protected final String[] loggerNames;
protected final List<LogRecord> logRecords = Collections.synchronizedList(new ArrayList<>());
protected final Map<Logger, Level> previousLevelsOfLoggersMap = new IdentityHashMap<>();
private volatile boolean installed = false;
protected final Handler handler = new Handler() {
@Override
public void publish(LogRecord record) {
logRecords.add(record);
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
logRecords.clear();
}
};
public LogCapture(Level level, String... loggerNames) {
this.level = (level == null ? Level.ALL : level);
this.loggerNames = loggerNames;
}
public void attach() {
if (!installed) {
for (String name : loggerNames) {
Logger logger = Logger.getLogger(name);
previousLevelsOfLoggersMap.put(logger, logger.getLevel());
logger.addHandler(handler);
logger.setLevel(level);
}
installed = true;
}
}
public boolean containsText(CharSequence s) {
for (LogRecord record : logRecords) {
if (record.getMessage().contains(s)) {
return true;
}
}
return false;
}
public boolean hasException(Class<? extends Throwable> type) {
for (LogRecord record : logRecords) {
Throwable t = record.getThrown();
while (t != null) {
if (type.isInstance(t)) {return true;}
t = t.getCause();
}
}
return false;
}
@Override
public void close() throws Exception {
for (Logger l : previousLevelsOfLoggersMap.keySet()) {
try {
l.removeHandler(handler);
} catch (Throwable ignore) {
}
try {
l.setLevel(previousLevelsOfLoggersMap.get(l));
} catch (Throwable ignore) {
}
}
previousLevelsOfLoggersMap.clear();
}
}
public static LogCapture attachLogCapture(Level level, String... loggerNames) {
LogCapture logCapture = new LogCapture(level, loggerNames);
logCapture.attach();
return logCapture;
}
/**
* Captures webapp-scoped logs (e.g. ContextConfig/Digester) during the
* CONFIGURE_START phase of a {@link Context}.
*/
public static class WebappLogCapture extends LogCapture implements LifecycleListener {
private String lifecycleEvent = Lifecycle.CONFIGURE_START_EVENT;
public WebappLogCapture(String lifecycleEvent, Level level, String... loggerNames) {
this(level, loggerNames);
this.lifecycleEvent = lifecycleEvent;
}
public WebappLogCapture(Level level, String... loggerNames) {
super(level, loggerNames);
}
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (this.lifecycleEvent.equals(event.getType())) {
this.attach();
}
}
}
/**
* Installs a {@link WebappLogCapture} on the given {@link Context} so it runs
* before {@link ContextConfig} during CONFIGURE_START.
* @param ctx the webapp context
* @param level level for loggers (e.g. {@code Level.ALL})
* @param loggerNames fully-qualified logger names
* @return the active capture
*/
public static WebappLogCapture attachWebappLogCapture(Context ctx, Level level, String... loggerNames) {
List<LifecycleListener> lifecycleListenersToReAdd = new ArrayList<>();
for (LifecycleListener l : ctx.findLifecycleListeners()) {
if (l instanceof ContextConfig) {
lifecycleListenersToReAdd.add(l);
}
}
for (LifecycleListener l : lifecycleListenersToReAdd) {
ctx.removeLifecycleListener(l);
}
WebappLogCapture webappLogCapture = new WebappLogCapture(level, loggerNames);
ctx.addLifecycleListener(webappLogCapture);
for (LifecycleListener l : lifecycleListenersToReAdd) {
ctx.addLifecycleListener(l);
}
return webappLogCapture;
}
/**
* Returns the localized key in a LocalStrings.properties file.
*
* @param packagePath The package that contains LocalStrings.properties, e.g. 'org.apache.catalina.startup'
* @param key The key to find, e.g. 'versionLoggerListener.serverInfo.server.built'
* @param locale The locale to use, e.g. Locale.ENGLISH
* @return The prefix before the first argument placeholder and if no placeholder, returns the whole formatted string.
*/
public static String getKeyFromPropertiesFile(String packagePath, String key, Locale locale) {
StringManager sm;
if (locale != null) {
sm = StringManager.getManager(packagePath, locale);
} else {
sm = StringManager.getManager(packagePath);
}
String formatted = sm.getString(key, "XXX");
int insertIndex = formatted.indexOf("XXX");
return (insertIndex == -1) ? formatted : formatted.substring(0, insertIndex);
}
public static String getKeyFromPropertiesFile(String packagePath, String key) {
return getKeyFromPropertiesFile(packagePath, key, Locale.getDefault());
}
public static String getKeyFromPropertiesFile(StringManager sm, String key) {
String formatted = sm.getString(key, "XXX");
int insertIndex = formatted.indexOf("XXX");
return (insertIndex == -1) ? formatted : formatted.substring(0, insertIndex);
}
/**
* Injects a {@link LifecycleListener} to a {@link Context} of a {@link Container} that sends {@code ADD_CHILD_EVENT}.
* Useful when deploying with the Manager / HostConfig.
*/
public static class ContainerInjector implements ContainerListener, AutoCloseable {
private final Container container;
private final Predicate<Context> filter;
private final Consumer<Context> action;
private volatile boolean installed = false;
private String containerEvent = Container.ADD_CHILD_EVENT;
private ContainerInjector(Container container, Predicate<Context> filter, Consumer<Context> action, String containerEvent) {
this.container = container;
this.filter = filter;
this.action = action;
if (containerEvent != null) {
this.containerEvent = containerEvent;
}
container.addContainerListener(this);
}
public static ContainerInjector inject(Container container, Predicate<Context> filter, Consumer<Context> action) {
return new ContainerInjector(container, filter, action, null);
}
public static ContainerInjector inject(Container container, Predicate<Context> filter, Consumer<Context> action, String containerEvent) {
return new ContainerInjector(container, filter, action, containerEvent);
}
@Override
public void containerEvent(ContainerEvent event) {
if (this.containerEvent.equals(event.getType()) && !installed) {
Object data = event.getData();
if (data instanceof Context ctx) {
if (filter != null && filter.test(ctx)) {
action.accept(ctx);
installed = true;
}
}
}
}
@Override
public void close() {
container.removeContainerListener(this);
}
}
}