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

remm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new fe3f74debf Add extension points to support dead properties
fe3f74debf is described below

commit fe3f74debf0ae971918771a4399c8ba4eb3b6c54
Author: remm <r...@apache.org>
AuthorDate: Fri Oct 18 09:21:41 2024 +0200

    Add extension points to support dead properties
    
    With a base PROPPATCH implementation.
    Add a custom impl to the test to verify.
    Remove D:source property (removed from RFC 4918).
---
 .../apache/catalina/servlets/WebdavServlet.java    | 400 +++++++++++++++++----
 java/org/apache/catalina/util/XMLWriter.java       |  22 ++
 .../catalina/servlets/TestWebdavServlet.java       |  76 +++-
 webapps/docs/changelog.xml                         |   6 +
 4 files changed, 428 insertions(+), 76 deletions(-)

diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java 
b/java/org/apache/catalina/servlets/WebdavServlet.java
index 9fec057e5a..193fb8d4e0 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -134,7 +134,8 @@ import org.xml.sax.SAXException;
  * access will be able to edit content available via 
http://host:port/context/content using
  * http://host:port/context/webdavedit/content
  * <p>
- * There are some known limitations of this Servlet due to it not implementing 
the PROPPATCH method. Details of these
+ * There are some known limitations of this Servlet due to it not implementing 
PROPPATCH and PROPFIND methods support
+ * for dead properties. The Servlet does provide extension points to add 
support for some as required by user. Details of these
  * limitations and progress towards addressing them are being tracked under
  * <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=69046";>bug 
69046</a>.
  * </p>
@@ -446,6 +447,20 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     }
 
 
+    private String getEncodedPath(String path, WebResource resource, 
HttpServletRequest request) {
+        String href = getPathPrefix(request);
+        if ((href.endsWith("/")) && (path.startsWith("/"))) {
+            href += path.substring(1);
+        } else {
+            href += path;
+        }
+        if (resource.isDirectory() && (!href.endsWith("/"))) {
+            href += "/";
+        }
+
+        return rewriteUrl(href);
+    }
+
     @Override
     protected void doOptions(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
         resp.addHeader("DAV", "1,2");
@@ -475,8 +490,14 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             path = path.substring(0, path.length() - 1);
         }
 
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        if (isSpecialPath(path)) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
         // Properties which are to be displayed.
-        List<String> properties = new ArrayList<>();
+        List<Node> properties = new ArrayList<>();
         // Propfind depth
         int depth = maxDepth;
         // Propfind type
@@ -526,12 +547,7 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                                         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);
-                                            }
+                                            properties.add(currentNode2);
                                             break;
                                     }
                                 }
@@ -573,7 +589,10 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
         generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus", 
XMLWriter.OPENING);
 
         if (depth == 0) {
-            parseProperties(req, generatedXML, path, type, properties);
+            propfindResource(generatedXML, getEncodedPath(path, resource, req),
+                    path, type, properties, resource.isFile(),
+                    resource.getCreation(), resource.getLastModified(), 
resource.getContentLength(),
+                    getServletContext().getMimeType(resource.getName()), 
generateETag(resource));
         } else {
             // The stack always contains the object of the current level
             Deque<String> stack = new ArrayDeque<>();
@@ -585,9 +604,21 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             while ((!stack.isEmpty()) && (depth >= 0)) {
 
                 String currentPath = stack.remove();
-                parseProperties(req, generatedXML, currentPath, type, 
properties);
+
+                // Exclude any resource in the /WEB-INF and /META-INF 
subdirectories
+                if (isSpecialPath(currentPath)) {
+                    continue;
+                }
 
                 resource = resources.getResource(currentPath);
+                // File is in directory listing but doesn't appear to exist
+                // Broken symlink or odd permission settings?
+                if (resource.exists()) {
+                    propfindResource(generatedXML, getEncodedPath(currentPath, 
resource, req),
+                            currentPath, type, properties, resource.isFile(),
+                            resource.getCreation(), 
resource.getLastModified(), resource.getContentLength(),
+                            
getServletContext().getMimeType(resource.getName()), generateETag(resource));
+                }
 
                 if (resource.isDirectory() && (depth > 0)) {
 
@@ -627,16 +658,24 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
      * @param req  The Servlet request
      * @param resp The Servlet response
      *
+     * @throws ServletException If an error occurs
      * @throws IOException If an IO error occurs
      */
-    protected void doProppatch(HttpServletRequest req, HttpServletResponse 
resp) throws IOException {
+    protected void doProppatch(HttpServletRequest req, HttpServletResponse 
resp) throws ServletException, IOException {
 
         String path = getRelativePath(req);
+        if (path.length() > 1 && path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
 
         WebResource resource = resources.getResource(path);
         if (!checkIfHeaders(req, resp, resource)) {
             return;
         }
+        if (!resource.exists()) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
 
         if (readOnly) {
             resp.sendError(WebdavStatus.SC_FORBIDDEN);
@@ -648,8 +687,177 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             return;
         }
 
-        // FIXME: Compliant PROPPATCH processing
-        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+        DocumentBuilder documentBuilder = getDocumentBuilder();
+        ArrayList<ProppatchOperation> operations = new ArrayList<>();
+
+        try {
+            Document document = documentBuilder.parse(new 
InputSource(req.getInputStream()));
+
+            // Get the root element of the document
+            Element rootElement = document.getDocumentElement();
+            if (!"propertyupdate".equals(getDAVNode(rootElement))) {
+                resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+                return;
+            }
+            NodeList childList = rootElement.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:
+                        String nodeName = getDAVNode(currentNode);
+                        if ("set".equals(nodeName)) {
+                            NodeList setChildList = 
currentNode.getChildNodes();
+                            for (int j = 0; j < setChildList.getLength(); j++) 
{
+                                Node currentNode2 = setChildList.item(j);
+                                switch (currentNode2.getNodeType()) {
+                                    case Node.TEXT_NODE:
+                                        break;
+                                    case Node.ELEMENT_NODE:
+                                        if 
("prop".equals(getDAVNode(currentNode2))) {
+                                            NodeList propChildList = 
currentNode2.getChildNodes();
+                                            Node property = null;
+                                            for (int k = 0; k < 
propChildList.getLength(); k++) {
+                                                Node currentNode3 = 
propChildList.item(k);
+                                                switch 
(currentNode3.getNodeType()) {
+                                                    case Node.TEXT_NODE:
+                                                        break;
+                                                    case Node.ELEMENT_NODE:
+                                                        property = 
currentNode3;
+                                                        break;
+                                                }
+                                            }
+                                            if (property != null) {
+                                                operations.add(new 
ProppatchOperation(PropertyUpdateType.SET, property));
+                                            } else {
+                                                
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+                                                return;
+                                            }
+                                        }
+                                        break;
+                                }
+                            }
+                        }
+                        if ("remove".equals(nodeName)) {
+                            NodeList removeChildList = 
currentNode.getChildNodes();
+                            for (int j = 0; j < removeChildList.getLength(); 
j++) {
+                                Node currentNode2 = removeChildList.item(j);
+                                switch (currentNode2.getNodeType()) {
+                                    case Node.TEXT_NODE:
+                                        break;
+                                    case Node.ELEMENT_NODE:
+                                        if 
("prop".equals(getDAVNode(currentNode2))) {
+                                            NodeList propChildList = 
currentNode2.getChildNodes();
+                                            Node property = null;
+                                            for (int k = 0; k < 
propChildList.getLength(); k++) {
+                                                Node currentNode3 = 
propChildList.item(k);
+                                                switch 
(currentNode3.getNodeType()) {
+                                                    case Node.TEXT_NODE:
+                                                        break;
+                                                    case Node.ELEMENT_NODE:
+                                                        property = 
currentNode3;
+                                                        break;
+                                                }
+                                            }
+                                            if (property != null) {
+                                                operations.add(new 
ProppatchOperation(PropertyUpdateType.REMOVE, property));
+                                            } else {
+                                                
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+                                                return;
+                                            }
+                                        }
+                                        break;
+                                }
+                            }
+                        }
+                        break;
+                }
+            }
+        } catch (SAXException | IOException e) {
+            // Something went wrong - bad request
+            resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+            return;
+        }
+
+        proppatchResource(path, operations);
+
+        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);
+        generatedXML.writeElement("D", "response", XMLWriter.OPENING);
+
+        // Generating href element
+        generatedXML.writeElement("D", "href", XMLWriter.OPENING);
+        generatedXML.writeText(getEncodedPath(path, resource, req));
+        generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
+
+        for (ProppatchOperation operation : operations) {
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+            generatedXML.writeElement(operation.propertyNode.getPrefix(),
+                    operation.propertyNode.getNamespaceURI(),
+                    operation.propertyNode.getLocalName(), 
XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText("HTTP/1.1 " + 
String.valueOf(operation.getStatusCode()) + " ");
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            if (operation.getProtectedProperty() && operation.getStatusCode() 
== HttpServletResponse.SC_FORBIDDEN) {
+                generatedXML.writeElement("D", "error", XMLWriter.OPENING);
+                generatedXML.writeElement("D", 
"cannot-modify-protected-property", XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "error", XMLWriter.CLOSING);
+            }
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+        }
+
+        generatedXML.writeElement("D", "response", XMLWriter.CLOSING);
+        generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING);
+
+        generatedXML.sendData();
+
+    }
+
+
+    /**
+     * Apply proppatch to the specified path. This should be overriden by 
subclasses to provide
+     * useful behavior. The default implementation prevents setting protected 
properties
+     * (anything from the DAV: namespace), and sets 507 for a set attempt on 
dead properties.
+     *
+     * @param path the resource path on which to apply the proppatch
+     * @param operations the set and remove to apply, the final status codes 
of the result should be set on each operation
+     */
+    protected void proppatchResource(String path, 
ArrayList<ProppatchOperation> operations) {
+        boolean setProperty = false;
+        boolean protectedProperty = false;
+        // Check for the protected properties
+        for (ProppatchOperation operation : operations) {
+            if (operation.getUpdateType() == PropertyUpdateType.SET) {
+                setProperty = true;
+            }
+            if (operation.getProtectedProperty()) {
+                protectedProperty = true;
+                operation.setStatusCode(HttpServletResponse.SC_FORBIDDEN);
+            }
+        }
+        if (protectedProperty) {
+            for (ProppatchOperation operation : operations) {
+                if (!operation.getProtectedProperty()) {
+                    operation.setStatusCode(WebdavStatus.SC_FAILED_DEPENDENCY);
+                }
+            }
+        } else if (setProperty) {
+            // No dead property support
+            for (ProppatchOperation operation : operations) {
+                operation.setStatusCode(WebdavStatus.SC_INSUFFICIENT_STORAGE);
+            }
+        }
     }
 
 
@@ -666,6 +874,12 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
 
         String path = getRelativePath(req);
 
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        if (isSpecialPath(path)) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
         WebResource resource = resources.getResource(path);
         if (!checkIfHeaders(req, resp, resource)) {
             return;
@@ -1843,50 +2057,8 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     }
 
 
-    /**
-     * Propfind helper method.
-     *
-     * @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 parseProperties(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;
-        }
-
-        WebResource resource = resources.getResource(path);
-        if (!resource.exists()) {
-            // File is in directory listing but doesn't appear to exist
-            // Broken symlink or odd permission settings?
-            return;
-        }
-
-        String href = getPathPrefix(req);
-        if ((href.endsWith("/")) && (path.startsWith("/"))) {
-            href += path.substring(1);
-        } else {
-            href += path;
-        }
-        if (resource.isDirectory() && (!href.endsWith("/"))) {
-            href += "/";
-        }
-
-        String rewrittenUrl = rewriteUrl(href);
-
-        generatePropFindResponse(generatedXML, rewrittenUrl, path, type, 
properties, resource.isFile(),
-                resource.getCreation(), resource.getLastModified(), 
resource.getContentLength(),
-                getServletContext().getMimeType(resource.getName()), 
generateETag(resource));
-    }
-
-
-    private void generatePropFindResponse(XMLWriter generatedXML, String 
rewrittenUrl, String path, int propFindType,
-            List<String> properties, boolean isFile, long created, long 
lastModified,
+    private void propfindResource(XMLWriter generatedXML, String rewrittenUrl, 
String path, int propFindType,
+            List<Node> properties, boolean isFile, long created, long 
lastModified,
             long contentLength, String contentType, String eTag) {
 
         generatedXML.writeElement("D", "response", XMLWriter.OPENING);
@@ -1928,8 +2100,7 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                     generatedXML.writeElement("D", "collection", 
XMLWriter.NO_CONTENT);
                     generatedXML.writeElement("D", "resourcetype", 
XMLWriter.CLOSING);
                 }
-
-                generatedXML.writeProperty("D", "source", "");
+                propfindResource(path, null, false, generatedXML);
 
                 String supportedLocks = "<D:lockentry>" + 
"<D:lockscope><D:exclusive/></D:lockscope>" +
                         "<D:locktype><D:write/></D:locktype>" + 
"</D:lockentry>" + "<D:lockentry>" +
@@ -1964,8 +2135,8 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                     generatedXML.writeElement("D", "getlastmodified", 
XMLWriter.NO_CONTENT);
                 }
                 generatedXML.writeElement("D", "resourcetype", 
XMLWriter.NO_CONTENT);
-                generatedXML.writeElement("D", "source", XMLWriter.NO_CONTENT);
                 generatedXML.writeElement("D", "lockdiscovery", 
XMLWriter.NO_CONTENT);
+                propfindResource(path, null, true, generatedXML);
 
                 generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
                 generatedXML.writeElement("D", "status", XMLWriter.OPENING);
@@ -1977,15 +2148,20 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
 
             case FIND_BY_PROPERTY:
 
-                List<String> propertiesNotFound = new ArrayList<>();
+                List<Node> propertiesNotFound = new ArrayList<>();
 
                 // Parse the list of properties
 
                 generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
                 generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
 
-                for (String property : properties) {
-                    if (property.equals("creationdate")) {
+                for (Node propertyNode : properties) {
+                    String property = getDAVNode(propertyNode);
+                    if (property == null) {
+                        if (!propfindResource(path, propertyNode, false, 
generatedXML)) {
+                            propertiesNotFound.add(propertyNode);
+                        }
+                    } else if (property.equals("creationdate")) {
                         generatedXML.writeProperty("D", "creationdate", 
getISOCreationDate(created));
                     } else if (property.equals("displayname")) {
                         generatedXML.writeElement("D", "displayname", 
XMLWriter.OPENING);
@@ -1995,32 +2171,32 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                         if (isFile) {
                             generatedXML.writeElement("D", 
"getcontentlanguage", XMLWriter.NO_CONTENT);
                         } else {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else if (property.equals("getcontentlength")) {
                         if (isFile) {
                             generatedXML.writeProperty("D", 
"getcontentlength", Long.toString(contentLength));
                         } else {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else if (property.equals("getcontenttype")) {
                         if (isFile) {
                             generatedXML.writeProperty("D", "getcontenttype", 
contentType);
                         } else {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else if (property.equals("getetag")) {
                         if (isFile) {
                             generatedXML.writeProperty("D", "getetag", eTag);
                         } else {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else if (property.equals("getlastmodified")) {
                         if (isFile) {
                             generatedXML.writeProperty("D", "getlastmodified",
                                     
FastHttpDateFormat.formatDate(lastModified));
                         } else {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else if (property.equals("resourcetype")) {
                         if (isFile) {
@@ -2030,8 +2206,6 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                             generatedXML.writeElement("D", "collection", 
XMLWriter.NO_CONTENT);
                             generatedXML.writeElement("D", "resourcetype", 
XMLWriter.CLOSING);
                         }
-                    } else if (property.equals("source")) {
-                        generatedXML.writeProperty("D", "source", "");
                     } else if (property.equals("supportedlock")) {
                         supportedLocks = "<D:lockentry>" + 
"<D:lockscope><D:exclusive/></D:lockscope>" +
                                 "<D:locktype><D:write/></D:locktype>" + 
"</D:lockentry>" + "<D:lockentry>" +
@@ -2042,10 +2216,10 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                         generatedXML.writeElement("D", "supportedlock", 
XMLWriter.CLOSING);
                     } else if (property.equals("lockdiscovery")) {
                         if (!generateLockDiscovery(path, generatedXML)) {
-                            propertiesNotFound.add(property);
+                            propertiesNotFound.add(propertyNode);
                         }
                     } else {
-                        propertiesNotFound.add(property);
+                        propertiesNotFound.add(propertyNode);
                     }
                 }
 
@@ -2062,8 +2236,15 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
                     generatedXML.writeElement("D", "propstat", 
XMLWriter.OPENING);
                     generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
 
-                    for (String propertyNotFound : propertiesNotFound) {
-                        generatedXML.writeElement("D", propertyNotFound, 
XMLWriter.NO_CONTENT);
+                    for (Node propertyNotFoundNode : propertiesNotFound) {
+                        String propertyNotFound = 
getDAVNode(propertyNotFoundNode);
+                        if (propertyNotFound != null) {
+                            generatedXML.writeElement("D", propertyNotFound, 
XMLWriter.NO_CONTENT);
+                        } else {
+                            
generatedXML.writeElement(propertyNotFoundNode.getPrefix(),
+                                    propertyNotFoundNode.getNamespaceURI(),
+                                    propertyNotFoundNode.getLocalName(), 
XMLWriter.NO_CONTENT);
+                        }
                     }
 
                     generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
@@ -2080,6 +2261,20 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     }
 
 
+    /**
+     * Generate propfind XML fragments for dead properties.
+     *
+     * @param path the resource path
+     * @param property the dead property, if null then all dead properties 
must be written
+     * @param nameOnly true if only the property name element should be 
generated
+     * @param generatedXML the current generated XML for the PROPFIND response
+     * @return true if property was specified and a corresponding dead 
property was found on the resource, false otherwise
+     */
+    protected boolean propfindResource(String path, Node property, boolean 
nameOnly, XMLWriter generatedXML) {
+        return false;
+    }
+
+
     /**
      * Print the lock discovery information associated with a path.
      *
@@ -2165,7 +2360,7 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
     }
 
 
-    private String getDAVNode(Node node) {
+    private static String getDAVNode(Node node) {
         if (node.getNamespaceURI().equals(DEFAULT_NAMESPACE)) {
             return node.getLocalName();
         }
@@ -2306,6 +2501,51 @@ public class WebdavServlet extends DefaultServlet 
implements PeriodicEventListen
             return new InputSource(new StringReader("Ignored external 
entity"));
         }
     }
+
+    enum PropertyUpdateType { SET, REMOVE }
+
+    protected static class ProppatchOperation {
+        private final PropertyUpdateType updateType;
+        private final Node propertyNode;
+        private final boolean protectedProperty;
+        private int statusCode = HttpServletResponse.SC_OK;
+        public ProppatchOperation(PropertyUpdateType updateType, Node 
propertyNode) {
+            this.updateType = updateType;
+            this.propertyNode = propertyNode;
+            protectedProperty = getDAVNode(propertyNode) != null;
+        }
+        /**
+         * @return the updateType
+         */
+        public PropertyUpdateType getUpdateType() {
+            return this.updateType;
+        }
+        /**
+         * @return the propertyNode
+         */
+        public Node getPropertyNode() {
+            return this.propertyNode;
+        }
+        /**
+         * @return the statusCode
+         */
+        public int getStatusCode() {
+            return this.statusCode;
+        }
+        /**
+         * @param statusCode the statusCode to set
+         */
+        public void setStatusCode(int statusCode) {
+            this.statusCode = statusCode;
+        }
+        /**
+         * @return the protectedProperty
+         */
+        public boolean getProtectedProperty() {
+            return this.protectedProperty;
+        }
+    }
+
 }
 
 
@@ -2497,4 +2737,14 @@ class WebdavStatus {
      * contain a valid Lock-Info header, or the Lock-Info header identifies a 
lock held by another principal.
      */
     public static final int SC_LOCKED = 423;
+
+    /**
+     * Status code (424) indicating that another dependent operation failed.
+     */
+    public static final int SC_FAILED_DEPENDENCY = 424;
+
+    /**
+     * Status code (507) indicating that the server does not have enough 
storage to complete the operation.
+     */
+    public static final int SC_INSUFFICIENT_STORAGE = 507;
 }
diff --git a/java/org/apache/catalina/util/XMLWriter.java 
b/java/org/apache/catalina/util/XMLWriter.java
index a8789bbe84..01c2e2c35f 100644
--- a/java/org/apache/catalina/util/XMLWriter.java
+++ b/java/org/apache/catalina/util/XMLWriter.java
@@ -165,6 +165,28 @@ public class XMLWriter {
                     lastWriteWasOpen = false;
                     break;
             }
+        } else if ((namespaceInfo != null) && (namespaceInfo.length() > 0)) {
+            switch (type) {
+                case OPENING:
+                    if (lastWriteWasOpen) {
+                        buffer.append('\n');
+                    }
+                    buffer.append("<" + name + " xmlns=\"" + namespaceInfo + 
"\">");
+                    lastWriteWasOpen = true;
+                    break;
+                case CLOSING:
+                    buffer.append("</" + name + ">\n");
+                    lastWriteWasOpen = false;
+                    break;
+                case NO_CONTENT:
+                default:
+                    if (lastWriteWasOpen) {
+                        buffer.append('\n');
+                    }
+                    buffer.append("<" + name + " xmlns=\"" + namespaceInfo + 
"\"/>\n");
+                    lastWriteWasOpen = false;
+                    break;
+            }
         } else {
             switch (type) {
                 case OPENING:
diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java 
b/test/org/apache/catalina/servlets/TestWebdavServlet.java
index df5cb2832b..45ed9ce9db 100644
--- a/test/org/apache/catalina/servlets/TestWebdavServlet.java
+++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java
@@ -19,6 +19,7 @@ package org.apache.catalina.servlets;
 import java.io.File;
 import java.io.IOException;
 import java.io.StringReader;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -34,6 +35,7 @@ import org.apache.catalina.Wrapper;
 import org.apache.catalina.startup.SimpleHttpClient;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.catalina.util.XMLWriter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.websocket.server.WsContextListener;
 import org.xml.sax.InputSource;
@@ -202,6 +204,8 @@ public class TestWebdavServlet extends TomcatBaseTest {
             "<D:propfind xmlns:D=\"DAV:\">\n" +
             "  <D:prop>\n" +
             "    <D:getcontenttype/>\n" +
+            "    <T:customprop 
xmlns:T=\"http://tomcat.apache.org/testsuite\"/>\n" +
+            "    <T:othercustomprop 
xmlns:T=\"http://tomcat.apache.org/testsuite\"/>\n" +
             "    <D:getcontentlength/>\n" +
             "  </D:prop>\n" +
             "</D:propfind>";
@@ -212,6 +216,21 @@ public class TestWebdavServlet extends TomcatBaseTest {
             "  <D:propname/>\n" +
             "</D:propfind>";
 
+    private static final String PROPPATCH_PROPNAME =
+            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+            "<D:propertyupdate xmlns:D=\"DAV:\" 
xmlns:T=\"http://tomcat.apache.org/testsuite\";>\n" +
+            "  <D:set>\n" +
+            "    <D:prop>\n" +
+            "      <T:customprop 
xmlns:T=\"http://tomcat.apache.org/testsuite\";>\n" +
+            "        <T:myvalue/>\n" +
+            "      </T:customprop>\n" +
+            "    </D:prop>\n" +
+            "  </D:set>\n" +
+            "  <D:remove>\n" +
+            "    <D:prop><T:othercustomprop/></D:prop>\n" +
+            "  </D:remove>\n" +
+            "</D:propertyupdate>";
+
     @Test
     public void testBasicProperties() throws Exception {
         Tomcat tomcat = getTomcatInstance();
@@ -220,7 +239,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
         File tempWebapp = new File(getTemporaryDirectory(), 
"webdav-properties");
         Assert.assertTrue(tempWebapp.mkdirs());
         Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath());
-        Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
WebdavServlet());
+        Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new 
CustomWebdavServlet());
         webdavServlet.addInitParameter("listings", "true");
         webdavServlet.addInitParameter("secret", "foo");
         webdavServlet.addInitParameter("readonly", "false");
@@ -281,6 +300,18 @@ public class TestWebdavServlet extends TomcatBaseTest {
         Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, 
client.getStatusCode());
         
Assert.assertTrue(client.getResponseBody().contains("<D:getcontenttype>"));
         
Assert.assertFalse(client.getResponseBody().contains("<D:getlastmodified>"));
+        Assert.assertTrue(client.getResponseBody().contains("<T:myvalue/>"));
+
+        client.setRequest(new String[] { "PROPPATCH /file1.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Content-Length: " + PROPPATCH_PROPNAME.length() + 
SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF + PROPPATCH_PROPNAME });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, 
client.getStatusCode());
+        Assert.assertTrue(proppatchSuccess);
+        
Assert.assertTrue(client.getResponseBody().contains("<T:othercustomprop"));
 
     }
 
@@ -559,4 +590,47 @@ public class TestWebdavServlet extends TomcatBaseTest {
             return true;
         }
     }
+
+    private static boolean proppatchSuccess = false;
+
+    private class CustomWebdavServlet extends WebdavServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void proppatchResource(String path, 
ArrayList<ProppatchOperation> operations) {
+            for (ProppatchOperation operation : operations) {
+                if (operation.getUpdateType().equals(PropertyUpdateType.SET)
+                        && 
operation.getPropertyNode().getLocalName().equals("customprop")) {
+                    proppatchSuccess = true;
+                }
+                operation.setStatusCode(HttpServletResponse.SC_OK);
+            }
+        }
+
+        @Override
+        protected boolean propfindResource(String path, org.w3c.dom.Node 
property, boolean nameOnly, XMLWriter generatedXML) {
+            if (nameOnly) {
+                generatedXML.writeElement("T", 
"http://tomcat.apache.org/testsuite";, "customprop", XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("T", 
"http://tomcat.apache.org/testsuite";, "othercustomprop", XMLWriter.NO_CONTENT);
+            } else if (property == null) {
+                generatedXML.writeElement("T", 
"http://tomcat.apache.org/testsuite";, "customprop", XMLWriter.OPENING);
+                generatedXML.writeElement("T", "myvalue", 
XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("T", "customprop", 
XMLWriter.CLOSING);
+                generatedXML.writeElement("T", 
"http://tomcat.apache.org/testsuite";, "othercustomprop", XMLWriter.OPENING);
+                generatedXML.writeElement("T", "myothervalue", 
XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("T", "othercustomprop", 
XMLWriter.CLOSING);
+            } else if (property.getLocalName().equals("customprop")) {
+                generatedXML.writeElement("T", 
"http://tomcat.apache.org/testsuite";, "customprop", XMLWriter.OPENING);
+                generatedXML.writeElement("T", "myvalue", 
XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("T", "customprop", 
XMLWriter.CLOSING);
+                return true;
+            } else if (property.getLocalName().equals("othercustomprop")) {
+                return false;
+            }
+            return false;
+        }
+
+    }
+
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7d4e18af96..6e276efb75 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -180,6 +180,12 @@
         WebDAV Delete should remove any existing lock on successfully deleted
         resources. (remm)
       </fix>
+      <update>
+        Add base WebDAV <code>PROPPATCH</code> implementation, to be extended
+        by overriding the <code>proppatchResource</code> and
+        <code>propfindResource</code> methods of the WebDAV Servlet to
+        provide the desired level of support for dead properties. (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


Reply via email to