This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit ee3a13f9ef81d71a6d7af0b6d9f80756bca15c88
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Jan 20 16:40:44 2025 +0000

    serveSubpathOnly should apply to destinations as well
    
    When using the WebDAV servlet with serveSubpathOnly set to true, ensure
    that the destination for any requested WebDAV operation is also
    restricted to the sub-path.
---
 .../apache/catalina/servlets/WebdavServlet.java    |  7 +-
 .../catalina/servlets/TestWebdavServlet.java       | 77 +++++++++++++++++++++-
 webapps/docs/changelog.xml                         |  5 ++
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java 
b/java/org/apache/catalina/servlets/WebdavServlet.java
index 30b7722f7e..3368969bbe 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -2086,7 +2086,12 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
 
         // Cross-context operations aren't supported
         String reqContextPath = getPathPrefix(req);
-        if (!destinationPath.startsWith(reqContextPath + "/")) {
+        String expectedTargetPath = reqContextPath;
+        // Also ensure copy (and move) operations do not escape the configured 
sub-path when limited to the sub-path
+        if (serveSubpathOnly && req.getServletPath() != null) {
+            expectedTargetPath = expectedTargetPath + req.getServletPath();
+        }
+        if (!destinationPath.startsWith(expectedTargetPath + "/")) {
             resp.sendError(WebdavStatus.SC_FORBIDDEN);
             return false;
         }
diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java 
b/test/org/apache/catalina/servlets/TestWebdavServlet.java
index 007f3ead95..ef11d28ae1 100644
--- a/test/org/apache/catalina/servlets/TestWebdavServlet.java
+++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java
@@ -690,9 +690,84 @@ public class TestWebdavServlet extends TomcatBaseTest {
         Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, 
client.getStatusCode());
         Assert.assertFalse(client.getResponseBody().contains("/myfolder"));
         validateXml(client.getResponseBody());
-
     }
 
+
+    @Test
+    public void testCopyOutsideSubpath() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        // Create a temp webapp that can be safely written to
+        File tempWebapp = new File(getTemporaryDirectory(), "webdav-subpath");
+        File subPath = new File(tempWebapp, "aaa");
+        Assert.assertTrue(subPath.mkdirs());
+
+        Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
+        Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
+        webdavServlet.addInitParameter("listings", "true");
+        webdavServlet.addInitParameter("readonly", "false");
+        webdavServlet.addInitParameter("serveSubpathOnly", "true");
+        ctxt.addServletMappingDecoded("/aaa/*", "webdav");
+        tomcat.start();
+
+        ctxt.getResources().setCacheMaxSize(10);
+        ctxt.getResources().setCacheObjectMaxSize(1);
+
+        Client client = new Client();
+        client.setPort(getPort());
+
+        // Create a file
+        client.setRequest(new String[] { "PUT /aaa/file1.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Content-Length: 6" + SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF + CONTENT });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_CREATED, 
client.getStatusCode());
+
+        // Copy file1.txt to file2.txt
+        client.setRequest(new String[] { "COPY /aaa/file1.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Destination: http://localhost:"; + getPort() + 
"/aaa/file2.txt"  + SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_CREATED, 
client.getStatusCode());
+
+        // Move file2.txt to file3.txt
+        client.setRequest(new String[] { "MOVE /aaa/file2.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Destination: http://localhost:"; + getPort() + 
"/aaa/file3.txt"  + SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_CREATED, 
client.getStatusCode());
+
+        // Copy file1.txt outside sub-path
+        client.setRequest(new String[] { "COPY /aaa/file1.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Destination: http://localhost:"; + getPort() + "/file1.txt"  + 
SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, 
client.getStatusCode());
+
+        // Move file1.txt outside sub-path
+        client.setRequest(new String[] { "MOVE /aaa/file1.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Destination: http://localhost:"; + getPort() + "/file1.txt"  + 
SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, 
client.getStatusCode());
+}
+
+
     @Test
     public void testSharedLocks() throws Exception {
         Tomcat tomcat = getTomcatInstance();
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index a40d83ec54..8212628b47 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -154,6 +154,11 @@
         be confirmed that the JVM has been correctly configured, prevent the
         impacted web applications from starting. (markt)
       </add>
+      <fix>
+        When using the WebDAV servlet with <code>serveSubpathOnly</code> set to
+        <code>true</code>, ensure that the destination for any requested WebDAV
+        operation is also restricted to the sub-path. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to