This is an automated email from the ASF dual-hosted git repository. remm 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 f99dee941d Locking must check parent collections f99dee941d is described below commit f99dee941d463a2b43e3a01d48675ad170d738e2 Author: remm <r...@apache.org> AuthorDate: Wed Oct 16 10:17:22 2024 +0200 Locking must check parent collections Code cleanup. Call if processing in nearly all methods. Update fixmes. Update test for collection lock behavior fix. --- .../apache/catalina/servlets/WebdavServlet.java | 118 +++++++++++++-------- .../catalina/servlets/TestWebdavServlet.java | 63 ++++++----- webapps/docs/changelog.xml | 4 + 3 files changed, 118 insertions(+), 67 deletions(-) diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java b/java/org/apache/catalina/servlets/WebdavServlet.java index 59814d8150..ef399b27f6 100644 --- a/java/org/apache/catalina/servlets/WebdavServlet.java +++ b/java/org/apache/catalina/servlets/WebdavServlet.java @@ -403,11 +403,13 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { - if (!super.checkIfHeaders(request, response, resource)) { + // Skip regular HTTP evaluation for a null resource + if (resource != null && !super.checkIfHeaders(request, response, resource)) { return false; } - // TODO : Checking the WebDAV If header + // FIXME : Process the WebDAV If header + return true; } @@ -483,7 +485,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } // Properties which are to be displayed. - List<String> properties = null; + List<String> properties = new ArrayList<>(); // Propfind depth int depth = maxDepth; // Propfind type @@ -503,8 +505,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } } - Node propNode = null; - if (req.getContentLengthLong() > 0) { DocumentBuilder documentBuilder = getDocumentBuilder(); @@ -528,7 +528,22 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen String nodeName = getDAVNode(currentNode); if ("prop".equals(nodeName)) { type = FIND_BY_PROPERTY; - propNode = currentNode; + NodeList propChildList = currentNode.getChildNodes(); + for (int j = 0; j < propChildList.getLength(); j++) { + Node currentNode2 = propChildList.item(j); + switch (currentNode2.getNodeType()) { + case Node.TEXT_NODE: + break; + case Node.ELEMENT_NODE: + // href is a live property which is handled differently + String propertyName = getDAVNode(currentNode2); + // No support for non DAV: properties + if (propertyName != null) { + properties.add(propertyName); + } + break; + } + } } if ("propname".equals(nodeName)) { type = FIND_PROPERTY_NAMES; @@ -546,30 +561,10 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } } - if (type == FIND_BY_PROPERTY) { - properties = new ArrayList<>(); - // propNode must be non-null if type == FIND_BY_PROPERTY - @SuppressWarnings("null") - NodeList childList = propNode.getChildNodes(); - - for (int i = 0; i < childList.getLength(); i++) { - Node currentNode = childList.item(i); - switch (currentNode.getNodeType()) { - case Node.TEXT_NODE: - break; - case Node.ELEMENT_NODE: - // href is a live property which is handled differently - String propertyName = getDAVNode(currentNode); - // No support for non DAV: properties - if (propertyName != null) { - properties.add(propertyName); - } - break; - } - } - } - WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return; + } if (!resource.exists()) { int slash = path.lastIndexOf('/'); @@ -672,7 +667,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen /** - * PROPPATCH Method. + * PROPPATCH Method. Dead properties support is a SHOULD in the specification and are not implemented. * * @param req The Servlet request * @param resp The Servlet response @@ -681,18 +676,25 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen */ protected void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String path = getRelativePath(req); + + WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return; + } + if (readOnly) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } - if (isLocked(null, req)) { + if (isLocked(path, req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } - // FIXME - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); + // FIXME: Compliant PROPPATCH processing + resp.sendError(HttpServletResponse.SC_FORBIDDEN); } @@ -710,6 +712,9 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen String path = getRelativePath(req); WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return; + } // Can't create a collection if a resource already exists at the given // path @@ -769,6 +774,9 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return; + } if (resource.isDirectory()) { sendNotAllowed(req, resp); return; @@ -852,6 +860,10 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen resp.sendError(WebdavStatus.SC_LOCKED); return; } + WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return; + } LockInfo lock = new LockInfo(maxDepth); @@ -1032,10 +1044,23 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen lock.path = path; - WebResource resource = resources.getResource(path); - if (lockRequestType == LOCK_CREATION) { + // Check if a parent is already locked + Iterator<LockInfo> collectionLocksIterator = collectionLocks.iterator(); + while (collectionLocksIterator.hasNext()) { + LockInfo currentLock = collectionLocksIterator.next(); + if (currentLock.hasExpired()) { + collectionLocksIterator.remove(); + continue; + } + if (lock.path.startsWith(currentLock.path + "/") && (currentLock.isExclusive() || lock.isExclusive())) { + // A parent collection of this collection is locked + resp.setStatus(WebdavStatus.SC_LOCKED); + return; + } + } + // Generating lock id String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" + lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" + @@ -1050,14 +1075,14 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen // Checking if a child resource of this collection is // already locked List<String> lockPaths = new ArrayList<>(); - Iterator<LockInfo> collectionLocksIterator = collectionLocks.iterator(); + collectionLocksIterator = collectionLocks.iterator(); while (collectionLocksIterator.hasNext()) { LockInfo currentLock = collectionLocksIterator.next(); if (currentLock.hasExpired()) { collectionLocksIterator.remove(); continue; } - if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive())) { + if (currentLock.path.startsWith(lock.path + "/") && (currentLock.isExclusive() || lock.isExclusive())) { // A child collection of this collection is locked lockPaths.add(currentLock.path); } @@ -1067,7 +1092,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen resourceLocks.remove(currentLock.path); continue; } - if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive())) { + if (currentLock.path.startsWith(lock.path + "/") && (currentLock.isExclusive() || lock.isExclusive())) { // A child resource of this collection is locked lockPaths.add(currentLock.path); } @@ -1078,7 +1103,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen // One of the child paths was locked // We generate a multistatus error report - resp.setStatus(WebdavStatus.SC_CONFLICT); + resp.setStatus(WebdavStatus.SC_MULTI_STATUS); XMLWriter generatedXML = new XMLWriter(); generatedXML.writeXMLHeader(); @@ -1247,9 +1272,8 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } String path = getRelativePath(req); - - if (isLocked(path, req)) { - resp.sendError(WebdavStatus.SC_LOCKED); + WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { return; } @@ -1310,7 +1334,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen /** * Check to see if a resource is currently write locked. The method will look at the "If" header to make sure the - * client has give the appropriate lock tokens. + * client has demonstrated knowledge of the appropriate lock tokens. * * @param path The relative path * @param req Servlet request @@ -1329,6 +1353,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen ifHeader = ""; } + // Normally a lock token is not sent using this header String lockTokenHeader = req.getHeader("Lock-Token"); if (lockTokenHeader == null) { lockTokenHeader = ""; @@ -1413,6 +1438,9 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen resp.sendError(WebdavStatus.SC_NOT_FOUND); return false; } + if (!checkIfHeaders(req, resp, source)) { + return false; + } // Parsing destination header // See RFC 4918 @@ -1661,6 +1689,10 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen * @throws IOException If an IO error occurs */ private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp) throws IOException { + WebResource resource = resources.getResource(path); + if (!checkIfHeaders(req, resp, resource)) { + return false; + } return deleteResource(path, req, resp, true); } diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java b/test/org/apache/catalina/servlets/TestWebdavServlet.java index 154305b504..a3d3056a84 100644 --- a/test/org/apache/catalina/servlets/TestWebdavServlet.java +++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java @@ -380,7 +380,7 @@ public class TestWebdavServlet extends TomcatBaseTest { client.setRequest(new String[] { "PUT /myfolder/file4.txt HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + "Content-Length: 6" + SimpleHttpClient.CRLF + - "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + + "If: " + lockToken + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF + CONTENT }); client.connect(); @@ -397,10 +397,19 @@ public class TestWebdavServlet extends TomcatBaseTest { client.processRequest(true); Assert.assertEquals(WebdavStatus.SC_LOCKED, client.getStatusCode()); + // Unlock /myfolder + client.setRequest(new String[] { "UNLOCK /myfolder HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_NO_CONTENT, client.getStatusCode()); + client.setRequest(new String[] { "LOCK /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + "Content-Length: " + LOCK_BODY.length() + SimpleHttpClient.CRLF + - "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF + LOCK_BODY }); client.connect(); @@ -415,22 +424,11 @@ public class TestWebdavServlet extends TomcatBaseTest { } Assert.assertNotNull(lockTokenFile); - // Try to add /myfolder/file5.txt to myfolder with lock token but without lock null - client.setRequest(new String[] { "PUT /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + - "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + - "Content-Length: 6" + SimpleHttpClient.CRLF + - "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + - "Connection: Close" + SimpleHttpClient.CRLF + - SimpleHttpClient.CRLF + CONTENT }); - client.connect(); - client.processRequest(true); - Assert.assertEquals(WebdavStatus.SC_LOCKED, client.getStatusCode()); - // Same but with lock token and lock null client.setRequest(new String[] { "PUT /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + "Content-Length: 6" + SimpleHttpClient.CRLF + - "Lock-Token: " + lockToken + ", " + lockTokenFile + SimpleHttpClient.CRLF + + "If: " + lockTokenFile + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF + CONTENT }); client.connect(); @@ -441,13 +439,30 @@ public class TestWebdavServlet extends TomcatBaseTest { client.setRequest(new String[] { "UNLOCK /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + "Lock-Token: " + lockTokenFile + SimpleHttpClient.CRLF + - "If: " + lockToken + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF }); client.connect(); client.processRequest(true); Assert.assertEquals(HttpServletResponse.SC_NO_CONTENT, client.getStatusCode()); + // Lock /myfolder again + client.setRequest(new String[] { "LOCK /myfolder HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Content-Length: " + LOCK_BODY.length() + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + LOCK_BODY }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode()); + Assert.assertTrue(client.getResponseBody().contains("opaquelocktoken:")); + lockToken = null; + for (String header : client.getResponseHeaders()) { + if (header.startsWith("Lock-Token: ")) { + lockToken = header.substring("Lock-Token: ".length()); + } + } + Assert.assertNotNull(lockToken); + // Copy /myfolder/file5.txt to /myfolder/file6.txt without lock (should not work) client.setRequest(new String[] { "COPY /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + @@ -478,26 +493,26 @@ public class TestWebdavServlet extends TomcatBaseTest { client.processRequest(true); Assert.assertEquals(HttpServletResponse.SC_CREATED, client.getStatusCode()); - // Unlock /myfolder - client.setRequest(new String[] { "UNLOCK /myfolder HTTP/1.1" + SimpleHttpClient.CRLF + + // Verify that everything created is there + client.setRequest(new String[] { "PROPFIND / HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + - "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF }); client.connect(); client.processRequest(true); - Assert.assertEquals(HttpServletResponse.SC_NO_CONTENT, client.getStatusCode()); + Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, client.getStatusCode()); + Assert.assertFalse(client.getResponseBody().contains("/myfolder/file4.txt")); + Assert.assertTrue(client.getResponseBody().contains("/file7.txt")); - // Verify that everything created is there - client.setRequest(new String[] { "PROPFIND / HTTP/1.1" + SimpleHttpClient.CRLF + + // Unlock /myfolder again + client.setRequest(new String[] { "UNLOCK /myfolder HTTP/1.1" + SimpleHttpClient.CRLF + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Lock-Token: " + lockToken + SimpleHttpClient.CRLF + "Connection: Close" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF }); client.connect(); client.processRequest(true); - Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, client.getStatusCode()); - Assert.assertFalse(client.getResponseBody().contains("/myfolder/file4.txt")); - Assert.assertTrue(client.getResponseBody().contains("/file7.txt")); + Assert.assertEquals(HttpServletResponse.SC_NO_CONTENT, client.getStatusCode()); // Delete /myfolder client.setRequest(new String[] { "DELETE /myfolder HTTP/1.1" + SimpleHttpClient.CRLF + diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 6e5a27ba43..d9dbd5c979 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -152,6 +152,10 @@ <fix> Enforce <code>DAV:</code> namespace on WebDAV XML elements. (remm) </fix> + <fix> + Do not allow a new WebDAV lock on a child resource if a parent + collection is locked (RFC 4918 section 6.1). (remm) + </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