diff --git a/config b/config index 60a8a2a..d672a0f 100644 --- a/config +++ b/config @@ -27,6 +27,10 @@ certificate = /etc/apache2/ssl/server.crt key = /etc/apache2/ssl/server.key # Reverse DNS to resolve client address in logs dns_lookup = True +# base URL if / is not the CalDAV root. If set, must start with / +base_prefix = +# base URL if Radicale is running with / as CalDAV root, but in a subdir behind a proxy (like nginx). If set, must start with / +proxy_base_prefix = [encoding] diff --git a/radicale/config.py b/radicale/config.py index 943f88e..df2db6c 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -45,7 +45,9 @@ INITIAL_CONFIG = { "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt", "key": "/etc/apache2/ssl/server.key", - "dns_lookup": "True"}, + "dns_lookup": "True", + "base_prefix": "", + "proxy_base_prefix": ""}, "encoding": { "request": "utf-8", "stock": "utf-8"}, diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index a2141a4..6154d18 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -119,6 +119,11 @@ def _response(code): return "HTTP/1.1 %i %s" % (code, client.responses[code]) +def _href_with_proxy_base_prefix(href): + href = "%s%s" % (config.get("server", "proxy_base_prefix"), href) + return href.replace("//", "/") + + def name_from_path(path, collection): """Return Radicale item name from ``path``.""" collection_parts = collection.path.strip("/").split("/") @@ -174,7 +179,7 @@ def delete(path, collection): multistatus.append(response) href = ET.Element(_tag("D", "href")) - href.text = path + href.text = _href_with_proxy_base_prefix(path) response.append(href) status = ET.Element(_tag("D", "status")) @@ -226,8 +231,11 @@ def _propfind_response(path, item, props, user): response = ET.Element(_tag("D", "response")) href = ET.Element(_tag("D", "href")) - uri = item.url if is_collection else "%s/%s" % (path, item.name) - href.text = uri.replace("//", "/") + if is_collection: + uri = "%s%s" % (config.get("server", "base_prefix"), item.url) + else: + uri = "%s/%s" % (path, item.name) + href.text = _href_with_proxy_base_prefix(uri) response.append(href) propstat404 = ET.Element(_tag("D", "propstat")) @@ -247,14 +255,14 @@ def _propfind_response(path, item, props, user): element.text = item.etag elif tag == _tag("D", "principal-URL"): tag = ET.Element(_tag("D", "href")) - tag.text = path + tag.text = _href_with_proxy_base_prefix(path) element.append(tag) elif tag in (_tag("D", "principal-collection-set"), _tag("C", "calendar-user-address-set"), _tag("CR", "addressbook-home-set"), _tag("C", "calendar-home-set")): tag = ET.Element(_tag("D", "href")) - tag.text = path + tag.text = _href_with_proxy_base_prefix(path) element.append(tag) elif tag == _tag("C", "supported-calendar-component-set"): # This is not a Todo @@ -266,7 +274,8 @@ def _propfind_response(path, item, props, user): # pylint: enable=W0511 elif tag == _tag("D", "current-user-principal") and user: tag = ET.Element(_tag("D", "href")) - tag.text = "/%s/" % user + prefixed_path = "%s/%s/" % (config.get("server", "base_prefix"), user) + tag.text = _href_with_proxy_base_prefix(prefixed_path) element.append(tag) elif tag == _tag("D", "current-user-privilege-set"): privilege = ET.Element(_tag("D", "privilege")) @@ -390,7 +399,7 @@ def proppatch(path, xml_request, collection): multistatus.append(response) href = ET.Element(_tag("D", "href")) - href.text = path + href.text = _href_with_proxy_base_prefix(path) response.append(href) with collection.props as collection_props: @@ -433,13 +442,23 @@ def report(path, xml_request, collection): prop_element = root.find(_tag("D", "prop")) props = [prop.tag for prop in prop_element] + proxy_prefix = config.get("server", "proxy_base_prefix") + base_prefix = config.get("server", "base_prefix") + if collection: if root.tag in (_tag("C", "calendar-multiget"), _tag("CR", "addressbook-multiget")): # Read rfc4791-7.9 for info - hreferences = set( - href_element.text for href_element - in root.findall(_tag("D", "href"))) + hreferences = set() + for href_element in root.findall(_tag("D", "href")): + # skip elements that don't have the correct base prefixes + if not href_element.text.startswith(proxy_prefix): + continue + unprefixed = href_element.text[len(proxy_prefix):] + if not unprefixed.startswith(base_prefix): + continue + # we keep the base prefix here, to be aligned with other paths + hreferences.add(unprefixed) else: hreferences = (path,) # TODO: handle other filters @@ -461,8 +480,9 @@ def report(path, xml_request, collection): collection_timezones = collection.timezones for hreference in hreferences: + unprefixed_hreference = hreference[len(base_prefix):] # Check if the reference is an item or a collection - name = name_from_path(hreference, collection) + name = name_from_path(unprefixed_hreference, collection) if name: # Reference is an item path = "/".join(hreference.split("/")[:-1]) + "/" @@ -480,7 +500,7 @@ def report(path, xml_request, collection): multistatus.append(response) href = ET.Element(_tag("D", "href")) - href.text = "%s/%s" % (path.rstrip("/"), item.name) + href.text = _href_with_proxy_base_prefix("%s/%s" % (path.rstrip("/"), item.name)) response.append(href) propstat = ET.Element(_tag("D", "propstat"))