From a06e4ef0752b112b83690674aa130fd3ea165067 Mon Sep 17 00:00:00 2001 From: Giel van Schijndel Date: Sat, 9 Aug 2014 22:44:36 +0200 Subject: [PATCH] PROPFIND: correctly handle 'propall' and 'propnames' * Handle both the explicit 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 ) wasn't either * now lists all retrievable properties Signed-off-by: Giel van Schijndel --- radicale/xmlutils.py | 65 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 17531fd..d871d9a 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -77,6 +77,11 @@ CLARK_TAG_REGEX = re.compile(r""" (?P.*) # short tag name """, re.VERBOSE) +HUMAN_REGEX = re.compile(r""" + (?P[^:{}]*) # namespace abbreviation + : # : + (?P.*) # 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