diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efac321..b216972a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.7.6.dev * Extension: item verification on commandline * Improvement: catch lack of support of PERIOD in vobject <= 0.9.9 +* Fix: sharing: backmap of REPORT/PROPPATCH hrefs is now URL-encode-aware (edit of a shared collection failed when the principal contains '@') ## 3.7.5 * Add: [sharing] conversion_bday_summary_template (customize summary) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index 90145329..1fc5b6de 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -25,6 +25,7 @@ import socket import xml.etree.ElementTree as ET from http import client from typing import Dict, Optional, Union, cast +from urllib.parse import quote import defusedxml.ElementTree as DefusedET @@ -49,8 +50,8 @@ def xml_proppatch(base_prefix: str, path: str, href = ET.Element(xmlutils.make_clark("D:href")) href.text = xmlutils.make_href(base_prefix, path) if share: - # backmap - href.text = href.text.replace(share['PathMapped'], share['PathOrToken']) + # backmap; quote so the encoded href and raw share path compare + href.text = href.text.replace(quote(share['PathMapped']), quote(str(share['PathOrToken']))) response.append(href) # Create D:propstat element for props with status 200 OK propstat = ET.Element(xmlutils.make_clark("D:propstat")) diff --git a/radicale/app/report.py b/radicale/app/report.py index 807a923a..b1f4c3dc 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -31,7 +31,7 @@ import xml.etree.ElementTree as ET from http import client from typing import (Callable, Iterable, Iterator, List, Optional, Sequence, Tuple, Union) -from urllib.parse import unquote, urlparse +from urllib.parse import quote, unquote, urlparse import vobject import vobject.base @@ -741,9 +741,11 @@ def xml_item_response(base_prefix: str, href: str, href_element.text = xmlutils.make_href(base_prefix, href) logger.trace("REPORT/xml_report: href=%r base_prefix=%r", href_element.text, base_prefix) if share: - # backmap - if href_element.text.startswith(base_prefix + share['PathMapped']): - href_element.text = base_prefix + str(share['PathOrToken']) + href_element.text.removeprefix(base_prefix + share['PathMapped']) + # backmap; quote so the encoded href and raw share path compare + mapped = quote(share['PathMapped']) + token = quote(str(share['PathOrToken'])) + if href_element.text.startswith(base_prefix + mapped): + href_element.text = base_prefix + token + href_element.text.removeprefix(base_prefix + mapped) if share_bday_automap and href_element.text.endswith(".vcf"): href_element.text = href_element.text.removesuffix(".vcf") + ".ics" logger.trace("REPORT/xml_report: href=%r (backmapped)", href_element.text)