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

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

commit 603a959d18cf2e05f00956e4d91de7a49b99a674
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 98737e0329..a1af063378 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -2085,7 +2085,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 084cfcdf74..1c1bd53e3c 100644
--- a/test/org/apache/catalina/servlets/TestWebdavServlet.java
+++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java
@@ -689,9 +689,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 b757d2b441..2ad10b243c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -154,6 +154,11 @@
         <code>CrawlerSessionManagerValve</code>. Submitted by Brian Matzon.
         (remm)
       </fix>
+      <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