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
This commit is contained in:
Unrud 2020-01-19 18:53:05 +01:00
parent 262d76cc87
commit d3776e55fb
10 changed files with 200 additions and 198 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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")

View File

@ -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<namespace>[^}]*)}(?P<tag>.*)", re.VERBOSE)
HUMAN_REGEX = re.compile(r"(?P<namespace>[^:{}]*):(?P<tag>.*)", 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 '<?xml version="1.0"?>\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