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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]