Przeglądaj źródła

PROPFIND: correctly handle 'propall' and 'propnames'

 * Handle both the explicit <propall/> and its implicit variants
  - the missing request-body case was already handled
  - the empty request-body case wasn't
  - the explicit (a request-body containing <propall/>) wasn't either
 * <propnames/> now lists all retrievable properties

Signed-off-by: Giel van Schijndel <me@mortis.eu>
Giel van Schijndel 11 lat temu
rodzic
commit
a06e4ef075
1 zmienionych plików z 58 dodań i 7 usunięć
  1. 58 7
      radicale/xmlutils.py

+ 58 - 7
radicale/xmlutils.py

@@ -77,6 +77,11 @@ CLARK_TAG_REGEX = re.compile(r"""
     (?P<tag>.*)              # short tag name
     """, re.VERBOSE)
 
+HUMAN_REGEX = re.compile(r"""
+    (?P<namespaceabbrev>[^:{}]*)    # namespace abbreviation
+    :                               # :
+    (?P<tag>.*)                     # short tag name
+    """, re.VERBOSE)
 
 def _pretty_xml(element, level=0):
     """Indent an ElementTree ``element`` and its children."""
@@ -124,6 +129,13 @@ def _tag_from_clark(name):
     return name
 
 
+def _tag_from_human(name):
+    """Get an XML Clark notation tag from human-readable variant ``name``."""
+    match = HUMAN_REGEX.match(name)
+    if match and match.group("namespaceabbrev") in NAMESPACES:
+        return _tag(match.group("namespaceabbrev"), match.group("tag"))
+    return name
+
 def _response(code):
     """Return full W3C names from HTTP status codes."""
     return "HTTP/1.1 %i %s" % (code, client.responses[code])
@@ -218,29 +230,41 @@ def propfind(path, xml_request, collections, user=None):
 
     """
     # Reading request
-    if xml_request:
-        root = ET.fromstring(xml_request.encode("utf8"))
-        props = [prop.tag for prop in root.find(_tag("D", "prop"))]
-    else:
+    root = ET.fromstring(xml_request.encode("utf8")) if xml_request else None
+
+    # > A client may choose not to submit a request body.  An empty PROPFIND
+    # > request body MUST be treated as if it were an 'allprop' request.
+    top_tag = root[0] if root is not None else ET.Element(_tag("D", "allprop"))
+
+    props = ()
+    if   top_tag.tag == _tag("D", "allprop"):
         props = [_tag("D", "getcontenttype"),
                  _tag("D", "resourcetype"),
                  _tag("D", "displayname"),
                  _tag("D", "owner"),
                  _tag("D", "getetag"),
                  _tag("ICAL", "calendar-color"),
-                 _tag("CS", "getctag")]
+                 _tag("CS", "getctag"),
+                 _tag("C", "supported-calendar-component-set"),
+                 _tag("D", "supported-report-set"),
+            ]
+    elif top_tag.tag == _tag("D", "prop"):
+        props = [prop.tag for prop in top_tag]
 
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
 
     for collection in collections:
-        response = _propfind_response(path, collection, props, user)
+        if top_tag.tag == _tag("D", "propname"):
+            response = _propfind_response(path, collection, (), user, propnames=True)
+        else:
+            response = _propfind_response(path, collection, props, user)
         multistatus.append(response)
 
     return _pretty_xml(multistatus)
 
 
-def _propfind_response(path, item, props, user):
+def _propfind_response(path, item, props, user, propnames=False):
     """Build and return a PROPFIND response."""
     is_collection = isinstance(item, ical.Collection)
     if is_collection:
@@ -264,6 +288,33 @@ def _propfind_response(path, item, props, user):
     prop404 = ET.Element(_tag("D", "prop"))
     propstat404.append(prop404)
 
+    if propnames:
+        # Should list all properties that can be retrieved by the code below
+        prop200.append(ET.Element(_tag("D",  "getetag"                         )))
+        prop200.append(ET.Element(_tag("D",  "principal-URL"                   )))
+        prop200.append(ET.Element(_tag("D",  "principal-collection-set"        )))
+        prop200.append(ET.Element(_tag("C",  "calendar-user-address-set"       )))
+        prop200.append(ET.Element(_tag("CR", "addressbook-home-set"            )))
+        prop200.append(ET.Element(_tag("C",  "calendar-home-set"               )))
+        prop200.append(ET.Element(_tag("C",  "supported-calendar-component-set")))
+        prop200.append(ET.Element(_tag("D",  "current-user-privilege-set"      )))
+        prop200.append(ET.Element(_tag("D",  "supported-report-set"            )))
+        prop200.append(ET.Element(_tag("D",  "getcontenttype"                  )))
+        prop200.append(ET.Element(_tag("D",  "resourcetype"                    )))
+
+        if is_collection:
+            prop200.append(ET.Element(_tag("CS",   "getctag"                         )))
+            prop200.append(ET.Element(_tag("C",    "calendar-timezone"               )))
+            prop200.append(ET.Element(_tag("D",    "displayname"                     )))
+            prop200.append(ET.Element(_tag("ICAL", "calendar-color"                  )))
+            if item.owner_url:
+                prop200.append(ET.Element(_tag("D",  "owner")))
+
+            for tag in collection_props:
+                clark_tag = _tag_from_human(tag)
+                if prop200.find(clark_tag) is None:
+                    prop200.append(ET.Element(clark_tag))
+
     for tag in props:
         element = ET.Element(tag)
         is404 = False