/*
* 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.manager;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Test;
import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.authenticator.TestBasicAuthParser.BasicAuthHeader;
import org.apache.catalina.realm.MemoryRealm;
import org.apache.catalina.realm.MessageDigestCredentialHandler;
import org.apache.catalina.startup.Catalina;
import org.apache.catalina.startup.HostConfig;
import org.apache.catalina.startup.SimpleHttpClient;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.catalina.storeconfig.StoreConfigLifecycleListener;
import org.apache.catalina.util.IOTools;
import org.apache.catalina.util.ServerInfo;
import org.apache.catalina.util.URLEncoder;
public class TestManagerWebapp extends TomcatBaseTest {
public static final String CONFIG = "<?xml version=\"1.0\" ?>" +
"<tomcat-users xmlns=\"http://tomcat.apache.org/xml\"" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsi:schemaLocation=\"http://tomcat.apache.org/xml/tomcat-users.xsd\"" + " version=\"1.0\">" +
"<role rolename=\"admin\" />" +
"<user username=\"admin\" password=\"sekr3t\" roles=\"manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script\" />" +
"</tomcat-users>";
/**
* Integration test for the manager webapp (verify all main Servlets are working).
*
* @throws Exception if an error occurs
*/
@Test
public void testServlets() throws Exception {
// Avoid possible locking failure due to deployment with the manager in testDeploy
ignoreTearDown = true;
Tomcat tomcat = getTomcatInstance();
tomcat.setAddDefaultWebXmlToWebapp(false);
tomcat.addUser("admin", "sekr3t");
tomcat.addRole("admin", "manager-gui");
tomcat.addRole("admin", "manager-script");
tomcat.addRole("admin", "manager-jmx");
tomcat.addRole("admin", "manager-status");
File webappDir = new File(getBuildDirectory(), "webapps");
// Add manager webapp
File appDir = new File(webappDir, "manager");
tomcat.addWebapp(null, "/manager", appDir.getAbsolutePath());
tomcat.start();
SimpleHttpClient client = new SimpleHttpClient() {
@Override
public boolean isResponseBodyOK() {
return true;
}
};
client.setPort(getPort());
String basicHeader = (new BasicAuthHeader("Basic", "admin", "sekr3t")).getHeader().toString();
// @formatter:off
client.setRequest(new String[] {
"GET /manager/html HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_UNAUTHORIZED, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /manager/html HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/manager/css/manager.css"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/status HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("MiB"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/jmxproxy HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("Tomcat:type=ThreadPool,name="));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains(" - "));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/sessions?path=/manager HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("[1]"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/resources HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains(" - "));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/serverinfo HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("[" + ServerInfo.getServerInfo() + "]"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/vminfo HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("vmName: "));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/threaddump HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("http-"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/list HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/manager:running"));
tomcat.stop();
}
@Test
public void testDeploy() throws Exception {
// Avoid possible locking failure due to deployment with the manager
ignoreTearDown = true;
Tomcat tomcat = getTomcatInstance();
tomcat.setAddDefaultWebXmlToWebapp(false);
tomcat.getServer().addLifecycleListener(new StoreConfigLifecycleListener());
File configFile = new File(getTemporaryDirectory(), "tomcat-users-manager-deploy.xml");
try (PrintWriter writer = new PrintWriter(configFile)) {
writer.write(CONFIG);
}
addDeleteOnTearDown(configFile);
MemoryRealm memoryRealm = new MemoryRealm();
memoryRealm.setCredentialHandler(new MessageDigestCredentialHandler());
memoryRealm.setPathname(configFile.getAbsolutePath());
tomcat.getEngine().setRealm(memoryRealm);
File webappDir = new File(getBuildDirectory(), "webapps");
// Add manager webapp
File appDir = new File(webappDir, "manager");
Context ctx = tomcat.addWebapp(null, "/manager", appDir.getAbsolutePath());
// Add host config otherwise there's no JMX deployer bean
HostConfig hostConfig = new HostConfig();
ctx.getParent().addLifecycleListener(hostConfig);
tomcat.start();
SimpleHttpClient client = new SimpleHttpClient() {
// 10s default too low for some CI systems
@Override
public void connect() throws IOException {
connect(30000, 30000);
}
@Override
public boolean isResponseBodyOK() {
return true;
}
};
client.setPort(getPort());
String basicHeader = (new BasicAuthHeader("Basic", "admin", "sekr3t")).getHeader().toString();
appDir = new File(webappDir, "examples");
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/deploy?war=" + URLEncoder.QUERY.encode(appDir.getAbsolutePath(), StandardCharsets.UTF_8) + " HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("OK - "));
// @formatter:off
client.setRequest(new String[] {
"GET /examples/servlets/servlet/RequestInfoExample HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/examples/servlets/servlet/RequestInfoExample"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/stop?path=/examples HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /examples/servlets/servlet/RequestInfoExample HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/start?path=/examples HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /examples/servlets/servlet/RequestInfoExample HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/examples/servlets/servlet/RequestInfoExample"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/save HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/save?path=/examples HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
File serverXml = new File(tomcat.getServer().getCatalinaBase(), Catalina.SERVER_XML);
Assert.assertTrue(serverXml.canRead());
addDeleteOnTearDown(serverXml);
String serverXmlDump;
try (FileReader reader = new FileReader(serverXml); StringWriter writer = new StringWriter()) {
IOTools.flow(reader, writer);
serverXmlDump = writer.toString();
}
Assert.assertTrue(serverXmlDump.contains("StoreConfigLifecycleListener"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/reload?path=/examples HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/list HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/examples:running"));
// @formatter:off
client.setRequest(new String[] {
"GET /examples/servlets/servlet/RequestInfoExample HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/examples/servlets/servlet/RequestInfoExample"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/list HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertTrue(client.getResponseBody().contains("/examples:running"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/undeploy?path=/examples HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/list HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
Assert.assertFalse(client.getResponseBody().contains("/examples:running"));
// @formatter:off
client.setRequest(new String[] {
"GET /manager/text/findleaks?statusLine=true HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
// @formatter:on
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
tomcat.stop();
}
/*
* Test case for <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=57700">Bug 57700</a>.
*/
@Test
public void testBug57700() throws Exception {
ignoreTearDown = true;
Tomcat tomcat = getTomcatInstance();
tomcat.setAddDefaultWebXmlToWebapp(false);
File configFile = new File(getTemporaryDirectory(), "tomcat-users-manager-lifecycle.xml");
try (PrintWriter writer = new PrintWriter(configFile)) {
writer.write(CONFIG);
}
addDeleteOnTearDown(configFile);
MemoryRealm memoryRealm = new MemoryRealm();
memoryRealm.setCredentialHandler(new MessageDigestCredentialHandler());
memoryRealm.setPathname(configFile.getAbsolutePath());
tomcat.getEngine().setRealm(memoryRealm);
File webappDir = new File(getBuildDirectory(), "webapps");
File appDir = new File(webappDir, "manager");
Context ctx = tomcat.addWebapp(null, "/manager", appDir.getAbsolutePath());
HostConfig hostConfig = new HostConfig();
ctx.getParent().addLifecycleListener(hostConfig);
File appRoot = new File(webappDir, "bug57700");
Assert.assertTrue(appRoot.mkdirs() && appRoot.isDirectory());
addDeleteOnTearDown(appRoot);
try (@SuppressWarnings("unused") TomcatBaseTest.ContainerInjector ignored =
TomcatBaseTest.ContainerInjector.inject(ctx.getParent(),
c -> c.getPath().equals("/bug57700"),
c -> {
c.addLifecycleListener(new FailOnceListener());
Tomcat.initWebappDefaults(c);
})) {
tomcat.start();
SimpleHttpClient client = new SimpleHttpClient() {
@Override
public void connect() throws IOException {
connect(30000, 30000);
}
@Override
public boolean isResponseBodyOK() {
return true;
}
};
client.setPort(getPort());
String basicHeader = (new BasicAuthHeader("Basic", "admin", "sekr3t")).getHeader().toString();
client.setRequest(new String[]{
"GET /manager/text/deploy?war=" + URLEncoder.QUERY.encode(appRoot.getAbsolutePath(), StandardCharsets.UTF_8) + " HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF + CRLF
});
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
client.setRequest(new String[]{
"GET /bug57700 HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF + CRLF
});
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, client.getStatusCode());
client.setRequest(new String[]{
"GET /manager/text/start?path=/bug57700 HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF + CRLF
});
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
client.setRequest(new String[]{
"GET /bug57700 HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Connection: Close" + CRLF + CRLF
});
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_FOUND, client.getStatusCode());
client.setRequest(new String[] {
"GET /manager/text/undeploy?path=/bug57700 HTTP/1.1" + CRLF +
"Host: localhost" + CRLF +
"Authorization: " + basicHeader + CRLF +
"Connection: Close" + CRLF +
CRLF
});
client.connect();
client.processRequest(true);
Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
tomcat.stop();
}
}
private static class FailOnceListener implements LifecycleListener {
private volatile boolean firstRun = true;
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getLifecycle() instanceof Context) {
if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType()) && firstRun) {
firstRun = false;
throw new RuntimeException("Configuration failure in first run only");
}
}
}
}
}