From d3776e55fbf44877a8bb3b4aae61c501f257e804 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 19 Jan 2020 18:53:05 +0100 Subject: [PATCH] Rework XML helpers functions - Merge make_tag, tag_from_clark and tag_from_human into make_clark and make_human - Don't use RegEx for parsing --- radicale/app/__init__.py | 5 +- radicale/app/delete.py | 8 +- radicale/app/mkcalendar.py | 3 +- radicale/app/move.py | 4 +- radicale/app/propfind.py | 176 ++++++++++++++++++------------------- radicale/app/proppatch.py | 14 +-- radicale/app/put.py | 5 +- radicale/app/report.py | 68 +++++++------- radicale/item/filter.py | 28 +++--- radicale/xmlutils.py | 87 ++++++++++-------- 10 files changed, 200 insertions(+), 198 deletions(-) diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index 54f2fd1..e6bc51e 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -374,10 +374,9 @@ class Application( xml_declaration=True) return f.getvalue() - def _webdav_error_response(self, namespace, name, + def _webdav_error_response(self, human_tag, status=httputils.WEBDAV_PRECONDITION_FAILED[0]): """Generate XML error response.""" headers = {"Content-Type": "text/xml; charset=%s" % self._encoding} - content = self._write_xml_content( - xmlutils.webdav_error(namespace, name)) + content = self._write_xml_content(xmlutils.webdav_error(human_tag)) return status, headers, content diff --git a/radicale/app/delete.py b/radicale/app/delete.py index 02bdc0d..e4c8712 100644 --- a/radicale/app/delete.py +++ b/radicale/app/delete.py @@ -31,15 +31,15 @@ def xml_delete(base_prefix, path, collection, href=None): """ collection.delete(href) - multistatus = ET.Element(xmlutils.make_tag("D", "multistatus")) - response = ET.Element(xmlutils.make_tag("D", "response")) + multistatus = ET.Element(xmlutils.make_clark("D:multistatus")) + response = ET.Element(xmlutils.make_clark("D:response")) multistatus.append(response) - href = ET.Element(xmlutils.make_tag("D", "href")) + href = ET.Element(xmlutils.make_clark("D:href")) href.text = xmlutils.make_href(base_prefix, path) response.append(href) - status = ET.Element(xmlutils.make_tag("D", "status")) + status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(200) response.append(status) diff --git a/radicale/app/mkcalendar.py b/radicale/app/mkcalendar.py index 2896ab2..256a736 100644 --- a/radicale/app/mkcalendar.py +++ b/radicale/app/mkcalendar.py @@ -54,8 +54,7 @@ class ApplicationMkcalendarMixin: with self._storage.acquire_lock("w", user): item = next(self._storage.discover(path), None) if item: - return self._webdav_error_response( - "D", "resource-must-be-null") + return self._webdav_error_response("D:resource-must-be-null") parent_path = pathutils.unstrip_path( posixpath.dirname(pathutils.strip_path(path)), True) parent_item = next(self._storage.discover(parent_path), None) diff --git a/radicale/app/move.py b/radicale/app/move.py index 7573198..2e88ca3 100644 --- a/radicale/app/move.py +++ b/radicale/app/move.py @@ -74,8 +74,8 @@ class ApplicationMoveMixin: not to_item and to_collection.path != item.collection.path and to_collection.has_uid(item.uid)): - return self._webdav_error_response( - "C" if tag == "VCALENDAR" else "CR", "no-uid-conflict") + return self._webdav_error_response("%s:no-uid-conflict" % ( + "C" if tag == "VCALENDAR" else "CR")) to_href = posixpath.basename(pathutils.strip_path(to_path)) try: self._storage.move(item, to_collection, to_href) diff --git a/radicale/app/propfind.py b/radicale/app/propfind.py index efc94d1..80d0c48 100644 --- a/radicale/app/propfind.py +++ b/radicale/app/propfind.py @@ -41,26 +41,26 @@ def xml_propfind(base_prefix, path, xml_request, allowed_items, user, # 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 = (xml_request[0] if xml_request is not None else - ET.Element(xmlutils.make_tag("D", "allprop"))) + ET.Element(xmlutils.make_clark("D:allprop"))) props = () allprop = False propname = False - if top_tag.tag == xmlutils.make_tag("D", "allprop"): + if top_tag.tag == xmlutils.make_clark("D:allprop"): allprop = True - elif top_tag.tag == xmlutils.make_tag("D", "propname"): + elif top_tag.tag == xmlutils.make_clark("D:propname"): propname = True - elif top_tag.tag == xmlutils.make_tag("D", "prop"): + elif top_tag.tag == xmlutils.make_clark("D:prop"): props = [prop.tag for prop in top_tag] - if xmlutils.make_tag("D", "current-user-principal") in props and not user: + if xmlutils.make_clark("D:current-user-principal") in props and not user: # Ask for authentication # Returning the DAV:unauthenticated pseudo-principal as specified in # RFC 5397 doesn't seem to work with DAVdroid. return client.FORBIDDEN, None # Writing answer - multistatus = ET.Element(xmlutils.make_tag("D", "multistatus")) + multistatus = ET.Element(xmlutils.make_clark("D:multistatus")) for item, permission in allowed_items: write = permission == "w" @@ -83,9 +83,8 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, else: collection = item.collection - response = ET.Element(xmlutils.make_tag("D", "response")) - - href = ET.Element(xmlutils.make_tag("D", "href")) + response = ET.Element(xmlutils.make_clark("D:response")) + href = ET.Element(xmlutils.make_clark("D:href")) if is_collection: # Some clients expect collections to end with / uri = pathutils.unstrip_path(item.path, True) @@ -98,39 +97,39 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, if propname or allprop: props = [] # Should list all properties that can be retrieved by the code below - props.append(xmlutils.make_tag("D", "principal-collection-set")) - props.append(xmlutils.make_tag("D", "current-user-principal")) - props.append(xmlutils.make_tag("D", "current-user-privilege-set")) - props.append(xmlutils.make_tag("D", "supported-report-set")) - props.append(xmlutils.make_tag("D", "resourcetype")) - props.append(xmlutils.make_tag("D", "owner")) + props.append(xmlutils.make_clark("D:principal-collection-set")) + props.append(xmlutils.make_clark("D:current-user-principal")) + props.append(xmlutils.make_clark("D:current-user-privilege-set")) + props.append(xmlutils.make_clark("D:supported-report-set")) + props.append(xmlutils.make_clark("D:resourcetype")) + props.append(xmlutils.make_clark("D:owner")) if is_collection and collection.is_principal: - props.append(xmlutils.make_tag("C", "calendar-user-address-set")) - props.append(xmlutils.make_tag("D", "principal-URL")) - props.append(xmlutils.make_tag("CR", "addressbook-home-set")) - props.append(xmlutils.make_tag("C", "calendar-home-set")) + props.append(xmlutils.make_clark("C:calendar-user-address-set")) + props.append(xmlutils.make_clark("D:principal-URL")) + props.append(xmlutils.make_clark("CR:addressbook-home-set")) + props.append(xmlutils.make_clark("C:calendar-home-set")) if not is_collection or is_leaf: - props.append(xmlutils.make_tag("D", "getetag")) - props.append(xmlutils.make_tag("D", "getlastmodified")) - props.append(xmlutils.make_tag("D", "getcontenttype")) - props.append(xmlutils.make_tag("D", "getcontentlength")) + props.append(xmlutils.make_clark("D:getetag")) + props.append(xmlutils.make_clark("D:getlastmodified")) + props.append(xmlutils.make_clark("D:getcontenttype")) + props.append(xmlutils.make_clark("D:getcontentlength")) if is_collection: if is_leaf: - props.append(xmlutils.make_tag("D", "displayname")) - props.append(xmlutils.make_tag("D", "sync-token")) + props.append(xmlutils.make_clark("D:displayname")) + props.append(xmlutils.make_clark("D:sync-token")) if collection.get_meta("tag") == "VCALENDAR": - props.append(xmlutils.make_tag("CS", "getctag")) + props.append(xmlutils.make_clark("CS:getctag")) props.append( - xmlutils.make_tag("C", "supported-calendar-component-set")) + xmlutils.make_clark("C:supported-calendar-component-set")) meta = item.get_meta() for tag in meta: if tag == "tag": continue - clark_tag = xmlutils.tag_from_human(tag) + clark_tag = xmlutils.make_clark(tag) if clark_tag not in props: props.append(clark_tag) @@ -142,30 +141,30 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, for tag in props: element = ET.Element(tag) is404 = False - if tag == xmlutils.make_tag("D", "getetag"): + if tag == xmlutils.make_clark("D:getetag"): if not is_collection or is_leaf: element.text = item.etag else: is404 = True - elif tag == xmlutils.make_tag("D", "getlastmodified"): + elif tag == xmlutils.make_clark("D:getlastmodified"): if not is_collection or is_leaf: element.text = item.last_modified else: is404 = True - elif tag == xmlutils.make_tag("D", "principal-collection-set"): - tag = ET.Element(xmlutils.make_tag("D", "href")) + elif tag == xmlutils.make_clark("D:principal-collection-set"): + tag = ET.Element(xmlutils.make_clark("D:href")) tag.text = xmlutils.make_href(base_prefix, "/") element.append(tag) - elif (tag in (xmlutils.make_tag("C", "calendar-user-address-set"), - xmlutils.make_tag("D", "principal-URL"), - xmlutils.make_tag("CR", "addressbook-home-set"), - xmlutils.make_tag("C", "calendar-home-set")) and + elif (tag in (xmlutils.make_clark("C:calendar-user-address-set"), + xmlutils.make_clark("D:principal-URL"), + xmlutils.make_clark("CR:addressbook-home-set"), + xmlutils.make_clark("C:calendar-home-set")) and collection.is_principal and is_collection): - tag = ET.Element(xmlutils.make_tag("D", "href")) + tag = ET.Element(xmlutils.make_clark("D:href")) tag.text = xmlutils.make_href(base_prefix, path) element.append(tag) - elif tag == xmlutils.make_tag("C", "supported-calendar-component-set"): - human_tag = xmlutils.tag_from_clark(tag) + elif tag == xmlutils.make_clark("C:supported-calendar-component-set"): + human_tag = xmlutils.make_human_tag(tag) if is_collection and is_leaf: meta = item.get_meta(human_tag) if meta: @@ -173,94 +172,91 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, else: components = ("VTODO", "VEVENT", "VJOURNAL") for component in components: - comp = ET.Element(xmlutils.make_tag("C", "comp")) + comp = ET.Element(xmlutils.make_clark("C:comp")) comp.set("name", component) element.append(comp) else: is404 = True - elif tag == xmlutils.make_tag("D", "current-user-principal"): + elif tag == xmlutils.make_clark("D:current-user-principal"): if user: - tag = ET.Element(xmlutils.make_tag("D", "href")) + tag = ET.Element(xmlutils.make_clark("D:href")) tag.text = xmlutils.make_href(base_prefix, "/%s/" % user) element.append(tag) else: element.append(ET.Element( - xmlutils.make_tag("D", "unauthenticated"))) - elif tag == xmlutils.make_tag("D", "current-user-privilege-set"): - privileges = [("D", "read")] + xmlutils.make_clark("D:unauthenticated"))) + elif tag == xmlutils.make_clark("D:current-user-privilege-set"): + privileges = ["D:read"] if write: - privileges.append(("D", "all")) - privileges.append(("D", "write")) - privileges.append(("D", "write-properties")) - privileges.append(("D", "write-content")) - for ns, privilege_name in privileges: - privilege = ET.Element(xmlutils.make_tag("D", "privilege")) + privileges.append("D:all") + privileges.append("D:write") + privileges.append("D:write-properties") + privileges.append("D:write-content") + for human_tag in privileges: + privilege = ET.Element(xmlutils.make_clark("D:privilege")) privilege.append(ET.Element( - xmlutils.make_tag(ns, privilege_name))) + xmlutils.make_clark(human_tag))) element.append(privilege) - elif tag == xmlutils.make_tag("D", "supported-report-set"): + elif tag == xmlutils.make_clark("D:supported-report-set"): # These 3 reports are not implemented - reports = [ - ("D", "expand-property"), - ("D", "principal-search-property-set"), - ("D", "principal-property-search")] + reports = ["D:expand-property", + "D:principal-search-property-set", + "D:principal-property-search"] if is_collection and is_leaf: - reports.append(("D", "sync-collection")) + reports.append("D:sync-collection") if item.get_meta("tag") == "VADDRESSBOOK": - reports.append(("CR", "addressbook-multiget")) - reports.append(("CR", "addressbook-query")) + reports.append("CR:addressbook-multiget") + reports.append("CR:addressbook-query") elif item.get_meta("tag") == "VCALENDAR": - reports.append(("C", "calendar-multiget")) - reports.append(("C", "calendar-query")) - for ns, report_name in reports: - supported = ET.Element( - xmlutils.make_tag("D", "supported-report")) - report_tag = ET.Element(xmlutils.make_tag("D", "report")) - supported_report_tag = ET.Element( - xmlutils.make_tag(ns, report_name)) - report_tag.append(supported_report_tag) - supported.append(report_tag) - element.append(supported) - elif tag == xmlutils.make_tag("D", "getcontentlength"): + reports.append("C:calendar-multiget") + reports.append("C:calendar-query") + for human_tag in reports: + supported_report = ET.Element( + xmlutils.make_clark("D:supported-report")) + report_tag = ET.Element(xmlutils.make_clark("D:report")) + report_tag.append(ET.Element(xmlutils.make_clark(human_tag))) + supported_report.append(report_tag) + element.append(supported_report) + elif tag == xmlutils.make_clark("D:getcontentlength"): if not is_collection or is_leaf: element.text = str(len(item.serialize().encode(encoding))) else: is404 = True - elif tag == xmlutils.make_tag("D", "owner"): + elif tag == xmlutils.make_clark("D:owner"): # return empty elment, if no owner available (rfc3744-5.1) if collection.owner: - tag = ET.Element(xmlutils.make_tag("D", "href")) + tag = ET.Element(xmlutils.make_clark("D:href")) tag.text = xmlutils.make_href( base_prefix, "/%s/" % collection.owner) element.append(tag) elif is_collection: - if tag == xmlutils.make_tag("D", "getcontenttype"): + if tag == xmlutils.make_clark("D:getcontenttype"): if is_leaf: element.text = xmlutils.MIMETYPES[item.get_meta("tag")] else: is404 = True - elif tag == xmlutils.make_tag("D", "resourcetype"): + elif tag == xmlutils.make_clark("D:resourcetype"): if item.is_principal: - tag = ET.Element(xmlutils.make_tag("D", "principal")) + tag = ET.Element(xmlutils.make_clark("D:principal")) element.append(tag) if is_leaf: if item.get_meta("tag") == "VADDRESSBOOK": tag = ET.Element( - xmlutils.make_tag("CR", "addressbook")) + xmlutils.make_clark("CR:addressbook")) element.append(tag) elif item.get_meta("tag") == "VCALENDAR": - tag = ET.Element(xmlutils.make_tag("C", "calendar")) + tag = ET.Element(xmlutils.make_clark("C:calendar")) element.append(tag) - tag = ET.Element(xmlutils.make_tag("D", "collection")) + tag = ET.Element(xmlutils.make_clark("D:collection")) element.append(tag) - elif tag == xmlutils.make_tag("RADICALE", "displayname"): + elif tag == xmlutils.make_clark("RADICALE:displayname"): # Only for internal use by the web interface displayname = item.get_meta("D:displayname") if displayname is not None: element.text = displayname else: is404 = True - elif tag == xmlutils.make_tag("D", "displayname"): + elif tag == xmlutils.make_clark("D:displayname"): displayname = item.get_meta("D:displayname") if not displayname and is_leaf: displayname = item.path @@ -268,27 +264,27 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, element.text = displayname else: is404 = True - elif tag == xmlutils.make_tag("CS", "getctag"): + elif tag == xmlutils.make_clark("CS:getctag"): if is_leaf: element.text = item.etag else: is404 = True - elif tag == xmlutils.make_tag("D", "sync-token"): + elif tag == xmlutils.make_clark("D:sync-token"): if is_leaf: element.text, _ = item.sync() else: is404 = True else: - human_tag = xmlutils.tag_from_clark(tag) + human_tag = xmlutils.make_human_tag(tag) meta = item.get_meta(human_tag) if meta is not None: element.text = meta else: is404 = True # Not for collections - elif tag == xmlutils.make_tag("D", "getcontenttype"): + elif tag == xmlutils.make_clark("D:getcontenttype"): element.text = xmlutils.get_content_type(item, encoding) - elif tag == xmlutils.make_tag("D", "resourcetype"): + elif tag == xmlutils.make_clark("D:resourcetype"): # resourcetype must be returned empty for non-collection elements pass else: @@ -299,12 +295,12 @@ def xml_propfind_response(base_prefix, path, item, props, user, encoding, for status_code, childs in responses.items(): if not childs: continue - propstat = ET.Element(xmlutils.make_tag("D", "propstat")) + propstat = ET.Element(xmlutils.make_clark("D:propstat")) response.append(propstat) - prop = ET.Element(xmlutils.make_tag("D", "prop")) + prop = ET.Element(xmlutils.make_clark("D:prop")) prop.extend(childs) propstat.append(prop) - status = ET.Element(xmlutils.make_tag("D", "status")) + status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(status_code) propstat.append(status) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index d476785..242bd15 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -35,17 +35,17 @@ def xml_add_propstat_to(element, tag, status_number): ``status_number``. """ - propstat = ET.Element(xmlutils.make_tag("D", "propstat")) + propstat = ET.Element(xmlutils.make_clark("D:propstat")) element.append(propstat) - prop = ET.Element(xmlutils.make_tag("D", "prop")) + prop = ET.Element(xmlutils.make_clark("D:prop")) propstat.append(prop) - clark_tag = tag if "{" in tag else xmlutils.make_tag(*tag.split(":", 1)) + clark_tag = xmlutils.make_clark(tag) prop_tag = ET.Element(clark_tag) prop.append(prop_tag) - status = ET.Element(xmlutils.make_tag("D", "status")) + status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(status_number) propstat.append(status) @@ -60,11 +60,11 @@ def xml_proppatch(base_prefix, path, xml_request, collection): props_to_remove = xmlutils.props_from_request(xml_request, actions=("remove",)) - multistatus = ET.Element(xmlutils.make_tag("D", "multistatus")) - response = ET.Element(xmlutils.make_tag("D", "response")) + multistatus = ET.Element(xmlutils.make_clark("D:multistatus")) + response = ET.Element(xmlutils.make_clark("D:response")) multistatus.append(response) - href = ET.Element(xmlutils.make_tag("D", "href")) + href = ET.Element(xmlutils.make_clark("D:href")) href.text = xmlutils.make_href(base_prefix, path) response.append(href) diff --git a/radicale/app/put.py b/radicale/app/put.py index 428244b..6a256c6 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -201,9 +201,8 @@ class ApplicationPutMixin: prepared_item, = prepared_items if (item and item.uid != prepared_item.uid or not item and parent_item.has_uid(prepared_item.uid)): - return self._webdav_error_response( - "C" if tag == "VCALENDAR" else "CR", - "no-uid-conflict") + return self._webdav_error_response("%s:no-uid-conflict" % ( + "C" if tag == "VCALENDAR" else "CR")) href = posixpath.basename(pathutils.strip_path(path)) try: diff --git a/radicale/app/report.py b/radicale/app/report.py index 48a37f8..f8ea151 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -36,42 +36,42 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, Read rfc3253-3.6 for info. """ - multistatus = ET.Element(xmlutils.make_tag("D", "multistatus")) + multistatus = ET.Element(xmlutils.make_clark("D:multistatus")) if xml_request is None: return client.MULTI_STATUS, multistatus root = xml_request if root.tag in ( - xmlutils.make_tag("D", "principal-search-property-set"), - xmlutils.make_tag("D", "principal-property-search"), - xmlutils.make_tag("D", "expand-property")): + xmlutils.make_clark("D:principal-search-property-set"), + xmlutils.make_clark("D:principal-property-search"), + xmlutils.make_clark("D:expand-property")): # We don't support searching for principals or indirect retrieving of # properties, just return an empty result. # InfCloud asks for expand-property reports (even if we don't announce # support for them) and stops working if an error code is returned. logger.warning("Unsupported REPORT method %r on %r requested", - xmlutils.tag_from_clark(root.tag), path) + xmlutils.make_human_tag(root.tag), path) return client.MULTI_STATUS, multistatus - if (root.tag == xmlutils.make_tag("C", "calendar-multiget") and + if (root.tag == xmlutils.make_clark("C:calendar-multiget") and collection.get_meta("tag") != "VCALENDAR" or - root.tag == xmlutils.make_tag("CR", "addressbook-multiget") and + root.tag == xmlutils.make_clark("CR:addressbook-multiget") and collection.get_meta("tag") != "VADDRESSBOOK" or - root.tag == xmlutils.make_tag("D", "sync-collection") and + root.tag == xmlutils.make_clark("D:sync-collection") and collection.get_meta("tag") not in ("VADDRESSBOOK", "VCALENDAR")): logger.warning("Invalid REPORT method %r on %r requested", - xmlutils.tag_from_clark(root.tag), path) + xmlutils.make_human_tag(root.tag), path) return (client.CONFLICT, - xmlutils.webdav_error("D", "supported-report")) - prop_element = root.find(xmlutils.make_tag("D", "prop")) + xmlutils.webdav_error("D:supported-report")) + prop_element = root.find(xmlutils.make_clark("D:prop")) props = ( [prop.tag for prop in prop_element] if prop_element is not None else []) if root.tag in ( - xmlutils.make_tag("C", "calendar-multiget"), - xmlutils.make_tag("CR", "addressbook-multiget")): + xmlutils.make_clark("C:calendar-multiget"), + xmlutils.make_clark("CR:addressbook-multiget")): # Read rfc4791-7.9 for info hreferences = set() - for href_element in root.findall(xmlutils.make_tag("D", "href")): + for href_element in root.findall(xmlutils.make_clark("D:href")): href_path = pathutils.sanitize_path( unquote(urlparse(href_element.text).path)) if (href_path + "/").startswith(base_prefix + "/"): @@ -79,9 +79,9 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, else: logger.warning("Skipping invalid path %r in REPORT request on " "%r", href_path, path) - elif root.tag == xmlutils.make_tag("D", "sync-collection"): + elif root.tag == xmlutils.make_clark("D:sync-collection"): old_sync_token_element = root.find( - xmlutils.make_tag("D", "sync-token")) + xmlutils.make_clark("D:sync-token")) old_sync_token = "" if old_sync_token_element is not None and old_sync_token_element.text: old_sync_token = old_sync_token_element.text.strip() @@ -93,18 +93,18 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, logger.warning("Client provided invalid sync token %r: %s", old_sync_token, e, exc_info=True) return (client.CONFLICT, - xmlutils.webdav_error("D", "valid-sync-token")) + xmlutils.webdav_error("D:valid-sync-token")) hreferences = (pathutils.unstrip_path( posixpath.join(collection.path, n)) for n in names) # Append current sync token to response - sync_token_element = ET.Element(xmlutils.make_tag("D", "sync-token")) + sync_token_element = ET.Element(xmlutils.make_clark("D:sync-token")) sync_token_element.text = sync_token multistatus.append(sync_token_element) else: hreferences = (path,) filters = ( - root.findall("./%s" % xmlutils.make_tag("C", "filter")) + - root.findall("./%s" % xmlutils.make_tag("CR", "filter"))) + root.findall("./%s" % xmlutils.make_clark("C:filter")) + + root.findall("./%s" % xmlutils.make_clark("CR:filter"))) def retrieve_items(collection, hreferences, multistatus): """Retrieves all items that are referenced in ``hreferences`` from @@ -157,18 +157,18 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, def match(item, filter_): tag = collection_tag if (tag == "VCALENDAR" and - filter_.tag != xmlutils.make_tag("C", filter_)): + filter_.tag != xmlutils.make_clark("C:%s" % filter_)): if len(filter_) == 0: return True if len(filter_) > 1: raise ValueError("Filter with %d children" % len(filter_)) - if filter_[0].tag != xmlutils.make_tag("C", "comp-filter"): + if filter_[0].tag != xmlutils.make_clark("C:comp-filter"): raise ValueError("Unexpected %r in filter" % filter_[0].tag) return radicale_filter.comp_match(item, filter_[0]) if (tag == "VADDRESSBOOK" and - filter_.tag != xmlutils.make_tag("CR", filter_)): + filter_.tag != xmlutils.make_clark("CR:%s" % filter_)): for child in filter_: - if child.tag != xmlutils.make_tag("CR", "prop-filter"): + if child.tag != xmlutils.make_clark("CR:prop-filter"): raise ValueError("Unexpected %r in filter" % child.tag) test = filter_.get("test", "anyof") if test == "anyof": @@ -203,15 +203,15 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, for tag in props: element = ET.Element(tag) - if tag == xmlutils.make_tag("D", "getetag"): + if tag == xmlutils.make_clark("D:getetag"): element.text = item.etag found_props.append(element) - elif tag == xmlutils.make_tag("D", "getcontenttype"): + elif tag == xmlutils.make_clark("D:getcontenttype"): element.text = xmlutils.get_content_type(item, encoding) found_props.append(element) elif tag in ( - xmlutils.make_tag("C", "calendar-data"), - xmlutils.make_tag("CR", "address-data")): + xmlutils.make_clark("C:calendar-data"), + xmlutils.make_clark("CR:address-data")): element.text = item.serialize() found_props.append(element) else: @@ -228,26 +228,26 @@ def xml_report(base_prefix, path, xml_request, collection, encoding, def xml_item_response(base_prefix, href, found_props=(), not_found_props=(), found_item=True): - response = ET.Element(xmlutils.make_tag("D", "response")) + response = ET.Element(xmlutils.make_clark("D:response")) - href_tag = ET.Element(xmlutils.make_tag("D", "href")) + href_tag = ET.Element(xmlutils.make_clark("D:href")) href_tag.text = xmlutils.make_href(base_prefix, href) response.append(href_tag) if found_item: for code, props in ((200, found_props), (404, not_found_props)): if props: - propstat = ET.Element(xmlutils.make_tag("D", "propstat")) - status = ET.Element(xmlutils.make_tag("D", "status")) + propstat = ET.Element(xmlutils.make_clark("D:propstat")) + status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(code) - prop_tag = ET.Element(xmlutils.make_tag("D", "prop")) + prop_tag = ET.Element(xmlutils.make_clark("D:prop")) for prop in props: prop_tag.append(prop) propstat.append(prop_tag) propstat.append(status) response.append(propstat) else: - status = ET.Element(xmlutils.make_tag("D", "status")) + status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(404) response.append(status) diff --git a/radicale/item/filter.py b/radicale/item/filter.py index 59e410a..4c46dff 100644 --- a/radicale/item/filter.py +++ b/radicale/item/filter.py @@ -75,7 +75,7 @@ def comp_match(item, filter_, level=0): # Point #1 of rfc4791-9.7.1 return name == tag if len(filter_) == 1: - if filter_[0].tag == xmlutils.make_tag("C", "is-not-defined"): + if filter_[0].tag == xmlutils.make_clark("C:is-not-defined"): # Point #2 of rfc4791-9.7.1 return name != tag if name != tag: @@ -89,14 +89,14 @@ def comp_match(item, filter_, level=0): else list(getattr(item.vobject_item, "%s_list" % tag.lower()))) for child in filter_: - if child.tag == xmlutils.make_tag("C", "prop-filter"): + if child.tag == xmlutils.make_clark("C:prop-filter"): if not any(prop_match(comp, child, "C") for comp in components): return False - elif child.tag == xmlutils.make_tag("C", "time-range"): + elif child.tag == xmlutils.make_clark("C:time-range"): if not time_range_match(item.vobject_item, filter_[0], tag): return False - elif child.tag == xmlutils.make_tag("C", "comp-filter"): + elif child.tag == xmlutils.make_clark("C:comp-filter"): if not comp_match(item, child, level=level + 1): return False else: @@ -115,20 +115,20 @@ def prop_match(vobject_item, filter_, ns): # Point #1 of rfc4791-9.7.2 return name in vobject_item.contents if len(filter_) == 1: - if filter_[0].tag == xmlutils.make_tag("C", "is-not-defined"): + if filter_[0].tag == xmlutils.make_clark("C:is-not-defined"): # Point #2 of rfc4791-9.7.2 return name not in vobject_item.contents if name not in vobject_item.contents: return False # Point #3 and #4 of rfc4791-9.7.2 for child in filter_: - if ns == "C" and child.tag == xmlutils.make_tag("C", "time-range"): + if ns == "C" and child.tag == xmlutils.make_clark("C:time-range"): if not time_range_match(vobject_item, child, name): return False - elif child.tag == xmlutils.make_tag(ns, "text-match"): + elif child.tag == xmlutils.make_clark("%s:text-match" % ns): if not text_match(vobject_item, child, name, ns): return False - elif child.tag == xmlutils.make_tag(ns, "param-filter"): + elif child.tag == xmlutils.make_clark("%s:param-filter" % ns): if not param_filter_match(vobject_item, child, name, ns): return False else: @@ -464,10 +464,10 @@ def param_filter_match(vobject_item, filter_, parent_name, ns): children = getattr(vobject_item, "%s_list" % parent_name, []) condition = any(name in child.params for child in children) if len(filter_) > 0: - if filter_[0].tag == xmlutils.make_tag(ns, "text-match"): + if filter_[0].tag == xmlutils.make_clark("%s:text-match" % ns): return condition and text_match( vobject_item, filter_[0], parent_name, ns, name) - elif filter_[0].tag == xmlutils.make_tag(ns, "is-not-defined"): + elif filter_[0].tag == xmlutils.make_clark("%s:is-not-defined" % ns): return not condition else: return condition @@ -488,18 +488,18 @@ def simplify_prefilters(filters, collection_tag="VCALENDAR"): if collection_tag != "VCALENDAR": simple = False break - if (col_filter.tag != xmlutils.make_tag("C", "comp-filter") or + if (col_filter.tag != xmlutils.make_clark("C:comp-filter") or col_filter.get("name").upper() != "VCALENDAR"): simple = False continue simple &= len(col_filter) <= 1 for comp_filter in col_filter: - if comp_filter.tag != xmlutils.make_tag("C", "comp-filter"): + if comp_filter.tag != xmlutils.make_clark("C:comp-filter"): simple = False continue tag = comp_filter.get("name").upper() if comp_filter.find( - xmlutils.make_tag("C", "is-not-defined")) is not None: + xmlutils.make_clark("C:is-not-defined")) is not None: simple = False continue simple &= len(comp_filter) <= 1 @@ -507,7 +507,7 @@ def simplify_prefilters(filters, collection_tag="VCALENDAR"): if tag not in ("VTODO", "VEVENT", "VJOURNAL"): simple = False break - if time_filter.tag != xmlutils.make_tag("C", "time-range"): + if time_filter.tag != xmlutils.make_clark("C:time-range"): simple = False continue start = time_filter.get("start") diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 4ca334a..76961f2 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -23,7 +23,6 @@ Helper functions for XML. """ import copy -import re import xml.etree.ElementTree as ET from collections import OrderedDict from http import client @@ -54,9 +53,6 @@ for short, url in NAMESPACES.items(): NAMESPACES_REV[url] = short ET.register_namespace("" if short == "D" else short, url) -CLARK_TAG_REGEX = re.compile(r"{(?P[^}]*)}(?P.*)", re.VERBOSE) -HUMAN_REGEX = re.compile(r"(?P[^:{}]*):(?P.*)", re.VERBOSE) - def pretty_xml(element, level=0): """Indent an ElementTree ``element`` and its children.""" @@ -79,34 +75,47 @@ def pretty_xml(element, level=0): return '\n%s' % ET.tostring(element, "unicode") -def make_tag(short_name, local): - """Get XML Clark notation {uri(``short_name``)}``local``.""" - return "{%s}%s" % (NAMESPACES[short_name], local) +def make_clark(human_tag): + """Get XML Clark notation from human tag ``human_tag``. - -def tag_from_clark(name): - """Get a human-readable variant of the XML Clark notation tag ``name``. - - For a given name using the XML Clark notation, return a human-readable - variant of the tag name for known namespaces. Otherwise, return the name as - is. + If ``human_tag`` is already in XML Clark notation it is returned as-is. """ - match = CLARK_TAG_REGEX.match(name) - if match and match.group("namespace") in NAMESPACES_REV: - args = { - "ns": NAMESPACES_REV[match.group("namespace")], - "tag": match.group("tag")} - return "%(ns)s:%(tag)s" % args - return name + if human_tag.startswith("{"): + ns, tag = human_tag[len("{"):].split("}", maxsplit=1) + if not ns or not tag: + raise ValueError("Invalid XML tag: %r" % human_tag) + return human_tag + ns_prefix, tag = human_tag.split(":", maxsplit=1) + if not ns_prefix or not tag: + raise ValueError("Invalid XML tag: %r" % human_tag) + ns = NAMESPACES.get(ns_prefix) + if not ns: + raise ValueError("Unknown XML namespace prefix: %r" % human_tag) + return "{%s}%s" % (ns, tag) -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("namespace") in NAMESPACES: - return make_tag(match.group("namespace"), match.group("tag")) - return name +def make_human_tag(clark_tag): + """Replace known namespaces in XML Clark notation ``clark_tag`` with + prefix. + + If the namespace is not in ``NAMESPACES`` the tag is returned as-is. + + """ + if not clark_tag.startswith("{"): + ns_prefix, tag = clark_tag.split(":", maxsplit=1) + if not ns_prefix or not tag: + raise ValueError("Invalid XML tag: %r" % clark_tag) + if ns_prefix not in NAMESPACES: + raise ValueError("Unknown XML namespace prefix: %r" % clark_tag) + return clark_tag + ns, tag = clark_tag[len("{"):].split("}", maxsplit=1) + if not ns or not tag: + raise ValueError("Invalid XML tag: %r" % clark_tag) + ns_prefix = NAMESPACES_REV.get(ns) + if ns_prefix: + return "%s:%s" % (ns_prefix, tag) + return clark_tag def make_response(code): @@ -120,10 +129,10 @@ def make_href(base_prefix, href): return quote("%s%s" % (base_prefix, href)) -def webdav_error(namespace, name): +def webdav_error(human_tag): """Generate XML error message.""" - root = ET.Element(make_tag("D", "error")) - root.append(ET.Element(make_tag(namespace, name))) + root = ET.Element(make_clark("D:error")) + root.append(ET.Element(human_tag)) return root @@ -145,29 +154,29 @@ def props_from_request(xml_request, actions=("set", "remove")): return result for action in actions: - action_element = xml_request.find(make_tag("D", action)) + action_element = xml_request.find(make_clark("D:%s" % action)) if action_element is not None: break else: action_element = xml_request - prop_element = action_element.find(make_tag("D", "prop")) + prop_element = action_element.find(make_clark("D:prop")) if prop_element is not None: for prop in prop_element: - if prop.tag == make_tag("D", "resourcetype"): + if prop.tag == make_clark("D:resourcetype"): for resource_type in prop: - if resource_type.tag == make_tag("C", "calendar"): + if resource_type.tag == make_clark("C:calendar"): result["tag"] = "VCALENDAR" break - elif resource_type.tag == make_tag("CR", "addressbook"): + elif resource_type.tag == make_clark("CR:addressbook"): result["tag"] = "VADDRESSBOOK" break - elif prop.tag == make_tag("C", "supported-calendar-component-set"): - result[tag_from_clark(prop.tag)] = ",".join( + elif prop.tag == make_clark("C:supported-calendar-component-set"): + result[make_human_tag(prop.tag)] = ",".join( supported_comp.attrib["name"] for supported_comp in prop - if supported_comp.tag == make_tag("C", "comp")) + if supported_comp.tag == make_clark("C:comp")) else: - result[tag_from_clark(prop.tag)] = prop.text + result[make_human_tag(prop.tag)] = prop.text return result