This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit b1d6083d3b1a8a2bde6f03a935847953c1072f7e Author: opencode <[email protected]> AuthorDate: Wed Oct 7 11:31:59 2026 +0200 Allow re-adding a host that was previously removed with manager.xml Removing a host does not physically remove the host configuration directory. When the removed host had been created with the manager attribute, the manager.xml copy installed in the configuration directory for that host remained behind. Adding a host again with the same name and manager=true then failed with "Couldn't install manager.xml" because Files.copy() refuses to replace an existing target, so the host was never created. This is a common flow since the HTML add form checks the manager option by default. Copy manager.xml with StandardCopyOption.REPLACE_EXISTING so the template shipped with the running host manager application overwrites the leftover file, matching the behaviour of a fresh add. Add a test covering add, remove and re-add with manager=true. --- .../catalina/manager/host/HostManagerServlet.java | 6 +- .../catalina/manager/TestHostManagerWebapp.java | 95 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/java/org/apache/catalina/manager/host/HostManagerServlet.java b/java/org/apache/catalina/manager/host/HostManagerServlet.java index 4111306215..d1d0de5c04 100644 --- a/java/org/apache/catalina/manager/host/HostManagerServlet.java +++ b/java/org/apache/catalina/manager/host/HostManagerServlet.java @@ -24,6 +24,7 @@ import java.io.Serial; import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.StringTokenizer; import javax.management.InstanceNotFoundException; @@ -388,7 +389,10 @@ public class HostManagerServlet extends HttpServlet implements ContainerServlet return; } Path dest = new File(configBaseFile, "manager.xml").toPath(); - Files.copy(is, dest); + // Replace any manager.xml left behind by a previous host + // with the same name. Removing a host does not delete the + // configuration directory. + Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ioe) { writer.println(smClient.getString("hostManagerServlet.managerXml")); return; diff --git a/test/org/apache/catalina/manager/TestHostManagerWebapp.java b/test/org/apache/catalina/manager/TestHostManagerWebapp.java index 272c416f6e..a43ebbc829 100644 --- a/test/org/apache/catalina/manager/TestHostManagerWebapp.java +++ b/test/org/apache/catalina/manager/TestHostManagerWebapp.java @@ -297,4 +297,99 @@ public class TestHostManagerWebapp extends TomcatBaseTest { tomcat.stop(); } + + + /* + * Removing a host leaves the configuration directory in place. Re-adding + * a host with the same name using manager=true must then replace the + * leftover manager.xml rather than failing to install it. + */ + @Test + public void testAddRemoveReAddWithManager() throws Exception { + System.setProperty("catalina.home", getBuildDirectory().getAbsolutePath()); + Tomcat tomcat = getTomcatInstance(); + tomcat.setAddDefaultWebXmlToWebapp(false); + + File configFile = new File(getTemporaryDirectory(), "tomcat-users-host-manager-readd.xml"); + try (PrintWriter writer = new PrintWriter(configFile)) { + writer.write(TestManagerWebapp.CONFIG); + } + addDeleteOnTearDown(configFile); + + MemoryRealm memoryRealm = new MemoryRealm(); + memoryRealm.setCredentialHandler(new MessageDigestCredentialHandler()); + memoryRealm.setPathname(configFile.getAbsolutePath()); + tomcat.getEngine().setRealm(memoryRealm); + + File confFolder = new File(getTemporaryDirectory(), "conf"); + confFolder.mkdirs(); + File webappDir = new File(getBuildDirectory(), "webapps"); + + File appDir = new File(webappDir, "host-manager"); + tomcat.addWebapp(null, "/host-manager", appDir.getAbsolutePath()); + + getProgrammaticRootContext(); + + 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 /host-manager/text/add?name=rehost&manager=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()); + Assert.assertTrue(client.getResponseBody(), + client.getResponseBody().contains("OK - Host [rehost] added")); + File managerXml = new File(confFolder, tomcat.getEngine().getName() + "/rehost/manager.xml"); + Assert.assertTrue(managerXml.getAbsolutePath(), managerXml.isFile()); + + // @formatter:off + client.setRequest(new String[] { + "GET /host-manager/text/remove?name=rehost 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(), + client.getResponseBody().contains("OK - Removed host [rehost]")); + // The configuration directory is intentionally left in place + Assert.assertTrue(managerXml.getAbsolutePath(), managerXml.isFile()); + + // @formatter:off + client.setRequest(new String[] { + "GET /host-manager/text/add?name=rehost&manager=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()); + Assert.assertTrue(client.getResponseBody(), + client.getResponseBody().contains("OK - Host [rehost] added")); + + tomcat.stop(); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
