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

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


The following commit(s) were added to refs/heads/11.0.x by this push:
     new a96fffd184 Add a configurable limit for WebDAV XML request bodies
a96fffd184 is described below

commit a96fffd18487a29c0a30d36f00cb2b2d91f6d42c
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Apr 15 11:19:29 2026 +0100

    Add a configurable limit for WebDAV XML request bodies
    
    Only applies where the user does not already have write access
    to the resource.
---
 .../apache/catalina/servlets/WebdavServlet.java    | 69 ++++++++++++++-
 .../TestWebdavBoundedByteArrayOutputStream.java    | 98 ++++++++++++++++++++++
 .../catalina/servlets/TestWebdavServlet.java       | 83 ++++++++++++++++++
 webapps/docs/changelog.xml                         |  6 ++
 4 files changed, 254 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java 
b/java/org/apache/catalina/servlets/WebdavServlet.java
index adceaef970..a9e3dfc459 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -175,6 +175,8 @@ import org.xml.sax.SAXException;
  *  &lt;/init-param&gt;
  * </pre>
  * <p>
+ * By default, WebDAV request bodies for LOCK and PROPFIND are limited to 4096 
bytes. To change this limit, set the
+ * <code>maxRequestBodySize</code> <code>init-param</code> for the WebDAV 
servlet.
  *
  * @see <a href="https://tools.ietf.org/html/rfc4918";>RFC 4918</a>
  */
@@ -202,6 +204,12 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     private static final int MAX_DEPTH = 3;
 
 
+    /*
+     * Default max request body size.
+     */
+    private static final int DEFAULT_MAX_REQUEST_BODY_SIZE = 4096;
+
+
     /**
      * Default namespace.
      */
@@ -215,6 +223,7 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             "\n  
<D:lockentry><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>\n"
 +
                     "  
<D:lockentry><D:lockscope><D:shared/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>\n";
 
+
     /**
      * Simple date format for the creation date ISO representation (partial).
      */
@@ -227,6 +236,7 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
      */
     protected static final String LOCK_SCHEME = "urn:uuid:";
 
+
     // ----------------------------------------------------- Instance Variables
 
     /**
@@ -247,6 +257,9 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     private int maxDepth = MAX_DEPTH;
 
 
+    private int maxRequestBodySize = DEFAULT_MAX_REQUEST_BODY_SIZE;
+
+
     /**
      * Is access allowed via WebDAV to the special paths (/WEB-INF and 
/META-INF)?
      */
@@ -293,6 +306,10 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             maxDepth = 
Integer.parseInt(getServletConfig().getInitParameter("maxDepth"));
         }
 
+        if (getServletConfig().getInitParameter("maxRequestBodySize") != null) 
{
+            maxRequestBodySize = 
Integer.parseInt(getServletConfig().getInitParameter("maxRequestBodySize"));
+        }
+
         if (getServletConfig().getInitParameter("allowSpecialPaths") != null) {
             allowSpecialPaths = 
Boolean.parseBoolean(getServletConfig().getInitParameter("allowSpecialPaths"));
         }
@@ -829,13 +846,23 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             }
         }
 
+        // Short-cut if client provided a content length
+        if (req.getContentLengthLong() > maxRequestBodySize) {
+            resp.sendError(WebdavStatus.SC_REQUEST_TOO_LONG);
+            return;
+        }
+
         byte[] body;
-        try (InputStream is = req.getInputStream(); ByteArrayOutputStream os = 
new ByteArrayOutputStream()) {
+        try (InputStream is = req.getInputStream();
+                BoundedByteArrayOutputStream os = new 
BoundedByteArrayOutputStream(maxRequestBodySize)) {
             IOTools.flow(is, os);
             body = os.toByteArray();
         } catch (IOException ioe) {
             resp.sendError(WebdavStatus.SC_BAD_REQUEST);
             return;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            resp.sendError(WebdavStatus.SC_REQUEST_TOO_LONG);
+            return;
         }
         if (body.length > 0) {
             DocumentBuilder documentBuilder = getDocumentBuilder();
@@ -1389,13 +1416,23 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
 
         Node lockInfoNode = null;
 
+        // Short-cut if client provided a content length
+        if (req.getContentLengthLong() > maxRequestBodySize) {
+            resp.sendError(WebdavStatus.SC_REQUEST_TOO_LONG);
+            return;
+        }
+
         byte[] body;
-        try (InputStream is = req.getInputStream(); ByteArrayOutputStream os = 
new ByteArrayOutputStream()) {
+        try (InputStream is = req.getInputStream();
+                BoundedByteArrayOutputStream os = new 
BoundedByteArrayOutputStream(maxRequestBodySize)) {
             IOTools.flow(is, os);
             body = os.toByteArray();
         } catch (IOException ioe) {
             resp.sendError(WebdavStatus.SC_BAD_REQUEST);
             return;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            resp.sendError(WebdavStatus.SC_REQUEST_TOO_LONG);
+            return;
         }
         if (body.length > 0) {
             DocumentBuilder documentBuilder = getDocumentBuilder();
@@ -3020,6 +3057,34 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     }
 
 
+    static class BoundedByteArrayOutputStream extends ByteArrayOutputStream {
+
+        private final int sizeLimit;
+        private int size;
+
+        BoundedByteArrayOutputStream(int sizeLimit) {
+            super();
+            this.sizeLimit = sizeLimit;
+        }
+
+        @Override
+        public synchronized void write(int b) {
+            size++;
+            if (size > sizeLimit) {
+                throw new ArrayIndexOutOfBoundsException();
+            }
+            super.write(b);
+        }
+
+        @Override
+        public synchronized void write(byte[] b, int off, int len) {
+            size += len;
+            if (size > sizeLimit) {
+                throw new ArrayIndexOutOfBoundsException();
+            }
+            super.write(b, off, len);
+        }
+    }
 }
 
 
diff --git 
a/test/org/apache/catalina/servlets/TestWebdavBoundedByteArrayOutputStream.java 
b/test/org/apache/catalina/servlets/TestWebdavBoundedByteArrayOutputStream.java
new file mode 100644
index 0000000000..b3648c92f6
--- /dev/null
+++ 
b/test/org/apache/catalina/servlets/TestWebdavBoundedByteArrayOutputStream.java
@@ -0,0 +1,98 @@
+/*
+ * 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.servlets;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.servlets.WebdavServlet.BoundedByteArrayOutputStream;
+
+public class TestWebdavBoundedByteArrayOutputStream {
+
+    private static final int TEST_LIMIT = 10;
+    private static final byte[] ONE_BYTE_ARRAY = new byte[] { 0 };
+
+
+    @Test
+    public void testWriteByte() {
+        BoundedByteArrayOutputStream bbaos = new 
BoundedByteArrayOutputStream(TEST_LIMIT);
+
+        for (int i = 0; i < TEST_LIMIT; i++) {
+            bbaos.write(0);
+        }
+
+        try {
+            bbaos.write(0);
+            Assert.fail("Writing 11th byte failed to trigger error");
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Pass
+        }
+    }
+
+
+    @Test
+    public void testWriteByteArray() throws IOException {
+        BoundedByteArrayOutputStream bbaos = new 
BoundedByteArrayOutputStream(TEST_LIMIT);
+
+        for (int i = 0; i < TEST_LIMIT; i++) {
+            bbaos.write(ONE_BYTE_ARRAY);
+        }
+
+        try {
+            bbaos.write(ONE_BYTE_ARRAY);
+            Assert.fail("Writing 11th byte failed to trigger error");
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Pass
+        }
+    }
+
+
+    @Test
+    public void testWriteByteSubArray() {
+        BoundedByteArrayOutputStream bbaos = new 
BoundedByteArrayOutputStream(TEST_LIMIT);
+
+        for (int i = 0; i < TEST_LIMIT; i++) {
+            bbaos.write(ONE_BYTE_ARRAY, 0, 1);
+        }
+
+        try {
+            bbaos.write(ONE_BYTE_ARRAY, 0, 1);
+            Assert.fail("Writing 11th byte failed to trigger error");
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Pass
+        }
+    }
+
+
+    @Test
+    public void testWriteBytes() {
+        BoundedByteArrayOutputStream bbaos = new 
BoundedByteArrayOutputStream(TEST_LIMIT);
+
+        for (int i = 0; i < TEST_LIMIT; i++) {
+            bbaos.writeBytes(ONE_BYTE_ARRAY);
+        }
+
+        try {
+            bbaos.writeBytes(ONE_BYTE_ARRAY);
+            Assert.fail("Writing 11th byte failed to trigger error");
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Pass
+        }
+    }
+}
diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java 
b/test/org/apache/catalina/servlets/TestWebdavServlet.java
index 96016f345e..e08fbf1126 100644
--- a/test/org/apache/catalina/servlets/TestWebdavServlet.java
+++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java
@@ -75,6 +75,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), 
"webdav-specialpath"+UUID.randomUUID());
+        tempWebapp.deleteOnExit();
         Assert.assertTrue("Failed to mkdirs on 
"+tempWebapp.getCanonicalPath(),tempWebapp.mkdirs());
         Assert.assertTrue(new File(tempWebapp,"WEB-INF").mkdir());
         Assert.assertTrue(new File(tempWebapp,"META-INF").mkdir());
@@ -297,6 +298,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), 
"webdav-properties");
+        tempWebapp.deleteOnExit();
         Assert.assertTrue(tempWebapp.mkdirs());
         Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
         Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
@@ -441,6 +443,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), "webdav-webapp");
+        tempWebapp.deleteOnExit();
         Assert.assertTrue(tempWebapp.mkdirs());
         Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
         Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
@@ -922,6 +925,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), "webdav-subpath");
+        tempWebapp.deleteOnExit();
         File subPath = new File(tempWebapp, "aaa");
         Assert.assertTrue(subPath.mkdirs());
 
@@ -1018,6 +1022,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), "webdav-lock");
+        tempWebapp.deleteOnExit();
         Assert.assertTrue(tempWebapp.mkdirs());
         Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
         Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
@@ -1405,6 +1410,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), "webdav-if");
+        tempWebapp.deleteOnExit();
         File folder = new File(tempWebapp, 
"/myfolder/myfolder2/myfolder4/myfolder5");
         Assert.assertTrue(folder.mkdirs());
         File file = new File(folder, "myfile.txt");
@@ -1535,6 +1541,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
 
         // Create a temp webapp that can be safely written to
         File tempWebapp = new File(getTemporaryDirectory(), "webdav-store");
+        tempWebapp.deleteOnExit();
         Assert.assertTrue(tempWebapp.mkdirs());
         Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
         Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
@@ -1566,6 +1573,82 @@ public class TestWebdavServlet extends TomcatBaseTest {
         validateXml(client.getResponseBody());
     }
 
+
+    /*
+     * Only tests LOCK bodies exceeding limit. Other tests cover valid LOCK 
bodies.
+     */
+    @Test
+    public void testLockBodyLimit() throws Exception {
+        doTestLimit("LOCK", LOCK_BODY);
+    }
+
+
+    /*
+     * Only tests PROPFIND bodies exceeding limit. Other tests cover valid 
PROPFIND bodies.
+     */
+    @Test
+    public void testPropFindBodyLimit() throws Exception {
+        doTestLimit("PROPFIND", PROPFIND_PROP);
+    }
+
+
+    private void doTestLimit(String method, String requestBody) throws 
Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp");
+        Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+
+        Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
+        webdavServlet.addInitParameter("listings", "true");
+        webdavServlet.addInitParameter("secret", "foo");
+        webdavServlet.addInitParameter("readonly", "false");
+        webdavServlet.addInitParameter("useStrongETags", "true");
+        webdavServlet.addInitParameter("maxRequestBodySize", "10");
+
+        ctxt.addServletMappingDecoded("/*", "webdav");
+        tomcat.start();
+
+        // With content length
+        Client client = new Client();
+        client.setPort(getPort());
+
+        // @formatter:off
+        client.setRequest(new String[] {
+                method + " / HTTP/1.1" + CRLF +
+                    "Host: localhost:" + getPort() + CRLF +
+                    "Content-Length: " + requestBody.length() + CRLF +
+                    "Connection: Close" + CRLF +
+                    CRLF +
+                    requestBody
+                });
+        // @formatter:on
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(WebdavStatus.SC_REQUEST_TOO_LONG, 
client.getStatusCode());
+
+        // Without content length
+        client.reset();
+
+        // @formatter:off
+        client.setRequest(new String[] {
+                method + " / HTTP/1.1" + CRLF +
+                    "Host: localhost:" + getPort() + CRLF +
+                    "Transfer-Encoding: chunked" + CRLF +
+                    "Connection: Close" + CRLF +
+                    CRLF +
+                    Integer.toHexString(requestBody.length()) + CRLF +
+                    requestBody + CRLF +
+                    "0" + CRLF +
+                    CRLF
+                });
+        // @formatter:on
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(WebdavStatus.SC_REQUEST_TOO_LONG, 
client.getStatusCode());
+    }
+
+
     public static class CustomPropertyStore implements PropertyStore {
 
         private String propertyName = null;
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index f0015d3bf4..344b7df8b1 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -156,6 +156,12 @@
         that both Java and Windows are removing / have removed support for
         RC4-HMAC. The guide now uses AES256-SHA1. (markt)
       </fix>
+      <fix>
+        Add a new initialisation parameter for WebDAV,
+        <code>maxRequestBodySize</code> which limits the size of a WebDAV
+        request body for LOCK and PROPFIND. The default value is 4096 bytes.
+        (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to