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
commit a627da2b9b77c88e1d1fdce5d27dc07ca38575da Author: remm <r...@apache.org> AuthorDate: Thu Oct 17 10:40:52 2024 +0200 Remove WebDAV lock null support for compliance with RFC 4918 Section 7.3 and annex D deprecate lock nulls and define a new behavior. http://www.webdav.org/specs/rfc4918.html#lock-null Instead a lock on a non existing resource will create an empty file locked with a regular lock, which is of course far simpler. When removing the lock, the corresponding file will not go away. --- .../apache/catalina/servlets/WebdavServlet.java | 146 ++------------------- .../catalina/servlets/TestWebdavServlet.java | 2 +- webapps/docs/changelog.xml | 5 + 3 files changed, 16 insertions(+), 137 deletions(-) diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java b/java/org/apache/catalina/servlets/WebdavServlet.java index fe5fbfc123..6b79fe3498 100644 --- a/java/org/apache/catalina/servlets/WebdavServlet.java +++ b/java/org/apache/catalina/servlets/WebdavServlet.java @@ -16,6 +16,7 @@ */ package org.apache.catalina.servlets; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -222,16 +223,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen private final ConcurrentHashMap<String,LockInfo> resourceLocks = new ConcurrentHashMap<>(); - /** - * Repository of the lock-null resources. - * <p> - * Key : path of the collection containing the lock-null resource<br> - * Value : List of lock-null resource which are members of the collection. Each element of the List is the path - * associated with the lock-null resource. - */ - private final ConcurrentHashMap<String,CopyOnWriteArrayList<String>> lockNullResources = new ConcurrentHashMap<>(); - - /** * List of the inheritable collection locks. */ @@ -294,7 +285,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen for (LockInfo currentLock : resourceLocks.values()) { if (currentLock.hasExpired()) { resourceLocks.remove(currentLock.path); - removeLockNull(currentLock.path); } } Iterator<LockInfo> collectionLocksIterator = collectionLocks.iterator(); @@ -302,7 +292,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen LockInfo currentLock = collectionLocksIterator.next(); if (currentLock.hasExpired()) { collectionLocksIterator.remove(); - removeLockNull(currentLock.path); } } } @@ -567,30 +556,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen return; } - if (!resource.exists()) { - int slash = path.lastIndexOf('/'); - if (slash != -1) { - String parentPath = path.substring(0, slash); - List<String> currentLockNullResources = lockNullResources.get(parentPath); - if (currentLockNullResources != null) { - for (String lockNullPath : currentLockNullResources) { - if (lockNullPath.equals(path)) { - resp.setStatus(WebdavStatus.SC_MULTI_STATUS); - resp.setContentType("text/xml; charset=UTF-8"); - // Create multistatus object - XMLWriter generatedXML = new XMLWriter(resp.getWriter()); - generatedXML.writeXMLHeader(); - generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus", XMLWriter.OPENING); - parseLockNullProperties(req, generatedXML, lockNullPath, type, properties); - generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING); - generatedXML.sendData(); - return; - } - } - } - } - } - if (!resource.exists()) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; @@ -635,18 +600,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen stackBelow.addFirst(newPath); } - // Displaying the lock-null resources present in that - // collection - String lockPath = currentPath; - if (lockPath.endsWith("/")) { - lockPath = lockPath.substring(0, lockPath.length() - 1); - } - List<String> currentLockNullResources = lockNullResources.get(lockPath); - if (currentLockNullResources != null) { - for (String lockNullPath : currentLockNullResources) { - parseLockNullProperties(req, generatedXML, lockNullPath, type, properties); - } - } } if (stack.isEmpty()) { @@ -742,8 +695,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen if (resources.mkdir(path)) { resp.setStatus(WebdavStatus.SC_CREATED); - // Removing any lock-null resource which would be present - removeLockNull(path); } else { resp.sendError(WebdavStatus.SC_CONFLICT); } @@ -785,8 +736,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen super.doPut(req, resp); - // Removing any lock-null resource which would be present - removeLockNull(path); } @@ -1190,12 +1139,11 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen // Checking if a resource exists at this path if (!resource.exists()) { - - // "Creating" a lock-null resource - int slash = lock.path.lastIndexOf('/'); - String parentPath = lock.path.substring(0, slash); - - lockNullResources.computeIfAbsent(parentPath, k -> new CopyOnWriteArrayList<>()).add(lock.path); + // RFC 4918 removes lock null, instead an empty file is created + if (!resources.write(path, new ByteArrayInputStream(new byte[0]), false)) { + resp.sendError(WebdavStatus.SC_CONFLICT); + return; + } } lock.tokens.add(lockToken); @@ -1307,8 +1255,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen if (lock.tokens.isEmpty()) { resourceLocks.remove(path); - // Removing any lock-null resource which would be present - removeLockNull(path); } } @@ -1328,8 +1274,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } if (lock.tokens.isEmpty()) { collectionLocks.remove(lock); - // Removing any lock-null resource which would be present - removeLockNull(path); break; } } @@ -1598,10 +1542,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen resp.setStatus(WebdavStatus.SC_CREATED); } - // Removing any lock-null resource which would be present at - // the destination path - removeLockNull(destinationPath); - return true; } @@ -1946,52 +1886,14 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen String rewrittenUrl = rewriteUrl(href); - generatePropFindResponse(generatedXML, rewrittenUrl, path, type, properties, resource.isFile(), false, + generatePropFindResponse(generatedXML, rewrittenUrl, path, type, properties, resource.isFile(), resource.getCreation(), resource.getLastModified(), resource.getContentLength(), getServletContext().getMimeType(resource.getName()), generateETag(resource)); } - /** - * Propfind helper method. Displays the properties of a lock-null resource. - * - * @param req The servlet request - * @param generatedXML XML response to the Propfind request - * @param path Path of the current resource - * @param type Propfind type - * @param properties If the propfind type is find properties by name, then this List contains those properties - */ - private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, - List<String> properties) { - - // Exclude any resource in the /WEB-INF and /META-INF subdirectories - if (isSpecialPath(path)) { - return; - } - - // Retrieving the lock associated with the lock-null resource - LockInfo lock = resourceLocks.get(path); - - if (lock == null) { - return; - } - - String absoluteUri = req.getRequestURI(); - String relativePath = getRelativePath(req); - String toAppend = path.substring(relativePath.length()); - if (!toAppend.startsWith("/")) { - toAppend = "/" + toAppend; - } - - String rewrittenUrl = rewriteUrl(RequestUtil.normalize(absoluteUri + toAppend)); - - generatePropFindResponse(generatedXML, rewrittenUrl, path, type, properties, true, true, - lock.creationDate.getTime(), lock.creationDate.getTime(), 0, "", ""); - } - - private void generatePropFindResponse(XMLWriter generatedXML, String rewrittenUrl, String path, int propFindType, - List<String> properties, boolean isFile, boolean isLockNull, long created, long lastModified, + List<String> properties, boolean isFile, long created, long lastModified, long contentLength, String contentType, String eTag) { generatedXML.writeElement("D", "response", XMLWriter.OPENING); @@ -2026,13 +1928,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen generatedXML.writeProperty("D", "getcontenttype", contentType); } generatedXML.writeProperty("D", "getetag", eTag); - if (isLockNull) { - generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); - generatedXML.writeElement("D", "lock-null", XMLWriter.NO_CONTENT); - generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING); - } else { - generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT); - } + generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT); } else { generatedXML.writeProperty("D", "getlastmodified", FastHttpDateFormat.formatDate(lastModified)); generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); @@ -2135,13 +2031,7 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } } else if (property.equals("resourcetype")) { if (isFile) { - if (isLockNull) { - generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); - generatedXML.writeElement("D", "lock-null", XMLWriter.NO_CONTENT); - generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING); - } else { - generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT); - } + generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT); } else { generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); generatedXML.writeElement("D", "collection", XMLWriter.NO_CONTENT); @@ -2282,21 +2172,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen } - private void removeLockNull(String path) { - int slash = path.lastIndexOf('/'); - if (slash >= 0) { - String parentPath = path.substring(0, slash); - List<String> paths = lockNullResources.get(parentPath); - if (paths != null) { - paths.remove(path); - if (paths.isEmpty()) { - lockNullResources.remove(parentPath); - } - } - } - } - - private String getDAVNode(Node node) { if (node.getNamespaceURI().equals(DEFAULT_NAMESPACE)) { return node.getLocalName(); @@ -2330,7 +2205,6 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen String owner = ""; List<String> tokens = Collections.synchronizedList(new ArrayList<>()); long expiresAt = 0; - Date creationDate = new Date(); // ----------------------------------------------------- Public Methods diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java b/test/org/apache/catalina/servlets/TestWebdavServlet.java index 22688b6ddd..df5cb2832b 100644 --- a/test/org/apache/catalina/servlets/TestWebdavServlet.java +++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java @@ -442,7 +442,7 @@ public class TestWebdavServlet extends TomcatBaseTest { SimpleHttpClient.CRLF + CONTENT }); client.connect(); client.processRequest(true); - Assert.assertEquals(HttpServletResponse.SC_CREATED, client.getStatusCode()); + Assert.assertEquals(HttpServletResponse.SC_NO_CONTENT, client.getStatusCode()); // Verify that this also removes the lock by doing another PUT without the token client.setRequest(new String[] { "DELETE /myfolder/file5.txt HTTP/1.1" + SimpleHttpClient.CRLF + diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 34f10491a2..8c83a8f990 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -160,6 +160,11 @@ WebDAV Delete should remove any existing lock on successfully deleted resources. (remm) </fix> + <update> + Remove WebDAV lock null support in accordance with RFC 4918 section 7.3 + and annex D. Instead a lock on a non existing resource will create an + empty file locked with a regular lock. (remm) + </update> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org