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

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

commit f43fd51a6acb990c75950fdd5230cae3385f3b75
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 b3048b8670..70999a5c62 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 8ace88bf23..f02ae80f7b 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