Remove base_prefix and use SCRIPT_NAME instead

This conforms with the WSGI reference (PEP 333)
This commit is contained in:
Unrud 2016-09-04 20:15:08 +02:00
parent b85fc5bed6
commit dbaf58dbfe
5 changed files with 49 additions and 65 deletions

6
config
View File

@ -51,12 +51,6 @@
# Reverse DNS to resolve client address in logs # Reverse DNS to resolve client address in logs
#dns_lookup = True #dns_lookup = True
# Root URL of Radicale (starting and ending with a slash)
#base_prefix = /
# Possibility to allow URLs cleaned by a HTTP server, without the base_prefix
#can_skip_base_prefix = False
# Message displayed in the client when a password is needed # Message displayed in the client when a password is needed
#realm = Radicale - Password Required #realm = Radicale - Password Required

View File

@ -307,19 +307,11 @@ class Application:
headers = pprint.pformat(self.headers_log(environ)) headers = pprint.pformat(self.headers_log(environ))
self.logger.debug("Request headers:\n%s", headers) self.logger.debug("Request headers:\n%s", headers)
# Strip base_prefix from request URI # Sanitize base prefix
base_prefix = self.configuration.get("server", "base_prefix") environ["SCRIPT_NAME"] = storage.sanitize_path(
if environ["PATH_INFO"].startswith(base_prefix): environ.get("SCRIPT_NAME", "")).rstrip("/")
environ["PATH_INFO"] = environ["PATH_INFO"][len(base_prefix):] self.logger.debug("Sanitized script name: %s", environ["SCRIPT_NAME"])
elif self.configuration.get("server", "can_skip_base_prefix"): base_prefix = environ["SCRIPT_NAME"]
self.logger.debug(
"Prefix already stripped from path: %s", environ["PATH_INFO"])
else:
# Request path not starting with base_prefix, not allowed
self.logger.debug(
"Path not starting with prefix: %s", environ["PATH_INFO"])
return response(*NOT_ALLOWED)
# Sanitize request URI # Sanitize request URI
environ["PATH_INFO"] = storage.sanitize_path( environ["PATH_INFO"] = storage.sanitize_path(
unquote(environ["PATH_INFO"])) unquote(environ["PATH_INFO"]))
@ -376,7 +368,8 @@ class Application:
if is_valid_user: if is_valid_user:
try: try:
status, headers, answer = function(environ, path, user) status, headers, answer = function(
environ, base_prefix, path, user)
except socket.timeout: except socket.timeout:
return response(*REQUEST_TIMEOUT) return response(*REQUEST_TIMEOUT)
else: else:
@ -423,7 +416,7 @@ class Application:
content = None content = None
return content return content
def do_DELETE(self, environ, path, user): def do_DELETE(self, environ, base_prefix, path, user):
"""Manage DELETE request.""" """Manage DELETE request."""
if not self._access(user, path, "w"): if not self._access(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -438,12 +431,13 @@ class Application:
# ETag precondition not verified, do not delete item # ETag precondition not verified, do not delete item
return PRECONDITION_FAILED return PRECONDITION_FAILED
if isinstance(item, self.Collection): if isinstance(item, self.Collection):
answer = xmlutils.delete(path, item) answer = xmlutils.delete(base_prefix, path, item)
else: else:
answer = xmlutils.delete(path, item.collection, item.href) answer = xmlutils.delete(
base_prefix, path, item.collection, item.href)
return client.OK, {}, answer return client.OK, {}, answer
def do_GET(self, environ, path, user): def do_GET(self, environ, base_prefix, path, user):
"""Manage GET request.""" """Manage GET request."""
# Display a "Radicale works!" message if the root URL is requested # Display a "Radicale works!" message if the root URL is requested
if not path.strip("/"): if not path.strip("/"):
@ -471,12 +465,13 @@ class Application:
answer = item.serialize() answer = item.serialize()
return client.OK, headers, answer return client.OK, headers, answer
def do_HEAD(self, environ, path, user): def do_HEAD(self, environ, base_prefix, path, user):
"""Manage HEAD request.""" """Manage HEAD request."""
status, headers, answer = self.do_GET(environ, path, user) status, headers, answer = self.do_GET(
environ, base_prefix, path, user)
return status, headers, None return status, headers, None
def do_MKCALENDAR(self, environ, path, user): def do_MKCALENDAR(self, environ, base_prefix, path, user):
"""Manage MKCALENDAR request.""" """Manage MKCALENDAR request."""
if not self.authorized(user, path, "w"): if not self.authorized(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -492,7 +487,7 @@ class Application:
self.Collection.create_collection(path, props=props) self.Collection.create_collection(path, props=props)
return client.CREATED, {}, None return client.CREATED, {}, None
def do_MKCOL(self, environ, path, user): def do_MKCOL(self, environ, base_prefix, path, user):
"""Manage MKCOL request.""" """Manage MKCOL request."""
if not self.authorized(user, path, "w"): if not self.authorized(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -505,7 +500,7 @@ class Application:
self.Collection.create_collection(path, props=props) self.Collection.create_collection(path, props=props)
return client.CREATED, {}, None return client.CREATED, {}, None
def do_MOVE(self, environ, path, user): def do_MOVE(self, environ, base_prefix, path, user):
"""Manage MOVE request.""" """Manage MOVE request."""
to_url = urlparse(environ["HTTP_DESTINATION"]) to_url = urlparse(environ["HTTP_DESTINATION"])
if to_url.netloc != environ["HTTP_HOST"]: if to_url.netloc != environ["HTTP_HOST"]:
@ -542,7 +537,7 @@ class Application:
self.Collection.move(item, to_collection, to_href) self.Collection.move(item, to_collection, to_href)
return client.CREATED, {}, None return client.CREATED, {}, None
def do_OPTIONS(self, environ, path, user): def do_OPTIONS(self, environ, base_prefix, path, user):
"""Manage OPTIONS request.""" """Manage OPTIONS request."""
headers = { headers = {
"Allow": ", ".join( "Allow": ", ".join(
@ -550,7 +545,7 @@ class Application:
"DAV": DAV_HEADERS} "DAV": DAV_HEADERS}
return client.OK, headers, None return client.OK, headers, None
def do_PROPFIND(self, environ, path, user): def do_PROPFIND(self, environ, base_prefix, path, user):
"""Manage PROPFIND request.""" """Manage PROPFIND request."""
if not self._access(user, path, "r"): if not self._access(user, path, "r"):
return NOT_ALLOWED return NOT_ALLOWED
@ -569,13 +564,13 @@ class Application:
read_items, write_items = self.collect_allowed_items(items, user) read_items, write_items = self.collect_allowed_items(items, user)
headers = {"DAV": DAV_HEADERS, "Content-Type": "text/xml"} headers = {"DAV": DAV_HEADERS, "Content-Type": "text/xml"}
status, answer = xmlutils.propfind( status, answer = xmlutils.propfind(
path, content, read_items, write_items, user) base_prefix, path, content, read_items, write_items, user)
if status == client.FORBIDDEN: if status == client.FORBIDDEN:
return NOT_ALLOWED return NOT_ALLOWED
else: else:
return status, headers, answer return status, headers, answer
def do_PROPPATCH(self, environ, path, user): def do_PROPPATCH(self, environ, base_prefix, path, user):
"""Manage PROPPATCH request.""" """Manage PROPPATCH request."""
if not self.authorized(user, path, "w"): if not self.authorized(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -585,10 +580,10 @@ class Application:
if not isinstance(item, self.Collection): if not isinstance(item, self.Collection):
return WEBDAV_PRECONDITION_FAILED return WEBDAV_PRECONDITION_FAILED
headers = {"DAV": DAV_HEADERS, "Content-Type": "text/xml"} headers = {"DAV": DAV_HEADERS, "Content-Type": "text/xml"}
answer = xmlutils.proppatch(path, content, item) answer = xmlutils.proppatch(base_prefix, path, content, item)
return client.MULTI_STATUS, headers, answer return client.MULTI_STATUS, headers, answer
def do_PUT(self, environ, path, user): def do_PUT(self, environ, base_prefix, path, user):
"""Manage PUT request.""" """Manage PUT request."""
if not self._access(user, path, "w"): if not self._access(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -640,7 +635,7 @@ class Application:
headers = {"ETag": new_item.etag} headers = {"ETag": new_item.etag}
return client.CREATED, headers, None return client.CREATED, headers, None
def do_REPORT(self, environ, path, user): def do_REPORT(self, environ, base_prefix, path, user):
"""Manage REPORT request.""" """Manage REPORT request."""
if not self._access(user, path, "w"): if not self._access(user, path, "w"):
return NOT_ALLOWED return NOT_ALLOWED
@ -656,5 +651,5 @@ class Application:
else: else:
collection = item.collection collection = item.collection
headers = {"Content-Type": "text/xml"} headers = {"Content-Type": "text/xml"}
answer = xmlutils.report(path, content, collection) answer = xmlutils.report(base_prefix, path, content, collection)
return client.MULTI_STATUS, headers, answer return client.MULTI_STATUS, headers, answer

View File

@ -153,9 +153,6 @@ def serve(configuration, logger):
atexit.register(cleanup) atexit.register(cleanup)
logger.info("Starting Radicale") logger.info("Starting Radicale")
logger.debug(
"Base URL prefix: %s", configuration.get("server", "base_prefix"))
# Create collection servers # Create collection servers
servers = {} servers = {}
if configuration.getboolean("server", "ssl"): if configuration.getboolean("server", "ssl"):

View File

@ -41,8 +41,6 @@ INITIAL_CONFIG = {
"protocol": "PROTOCOL_SSLv23", "protocol": "PROTOCOL_SSLv23",
"ciphers": "", "ciphers": "",
"dns_lookup": "True", "dns_lookup": "True",
"base_prefix": "/",
"can_skip_base_prefix": "False",
"realm": "Radicale - Password Required"}, "realm": "Radicale - Password Required"},
"encoding": { "encoding": {
"request": "utf-8", "request": "utf-8",

View File

@ -102,11 +102,9 @@ def _response(code):
return "HTTP/1.1 %i %s" % (code, client.responses[code]) return "HTTP/1.1 %i %s" % (code, client.responses[code])
def _href(collection, href): def _href(base_prefix, href):
"""Return prefixed href.""" """Return prefixed href."""
return "%s%s" % ( return "%s%s" % (base_prefix, href)
collection.configuration.get("server", "base_prefix"),
href.lstrip("/"))
def _date_to_datetime(date_): def _date_to_datetime(date_):
@ -466,7 +464,7 @@ def props_from_request(root, actions=("set", "remove")):
return result return result
def delete(path, collection, href=None): def delete(base_prefix, path, collection, href=None):
"""Read and answer DELETE requests. """Read and answer DELETE requests.
Read rfc4918-9.6 for info. Read rfc4918-9.6 for info.
@ -479,7 +477,7 @@ def delete(path, collection, href=None):
multistatus.append(response) multistatus.append(response)
href = ET.Element(_tag("D", "href")) href = ET.Element(_tag("D", "href"))
href.text = _href(collection, path) href.text = _href(base_prefix, path)
response.append(href) response.append(href)
status = ET.Element(_tag("D", "status")) status = ET.Element(_tag("D", "status"))
@ -489,7 +487,8 @@ def delete(path, collection, href=None):
return _pretty_xml(multistatus) return _pretty_xml(multistatus)
def propfind(path, xml_request, read_collections, write_collections, user): def propfind(base_prefix, path, xml_request, read_collections,
write_collections, user):
"""Read and answer PROPFIND requests. """Read and answer PROPFIND requests.
Read rfc4918-9.1 for info. Read rfc4918-9.1 for info.
@ -522,19 +521,19 @@ def propfind(path, xml_request, read_collections, write_collections, user):
for collection in write_collections: for collection in write_collections:
collections.append(collection) collections.append(collection)
response = _propfind_response( response = _propfind_response(
path, collection, props, user, write=True) base_prefix, path, collection, props, user, write=True)
multistatus.append(response) multistatus.append(response)
for collection in read_collections: for collection in read_collections:
if collection in collections: if collection in collections:
continue continue
response = _propfind_response( response = _propfind_response(
path, collection, props, user, write=False) base_prefix, path, collection, props, user, write=False)
multistatus.append(response) multistatus.append(response)
return client.MULTI_STATUS, _pretty_xml(multistatus) return client.MULTI_STATUS, _pretty_xml(multistatus)
def _propfind_response(path, item, props, user, write=False): def _propfind_response(base_prefix, path, item, props, user, write=False):
"""Build and return a PROPFIND response.""" """Build and return a PROPFIND response."""
is_collection = isinstance(item, storage.BaseCollection) is_collection = isinstance(item, storage.BaseCollection)
if is_collection: if is_collection:
@ -552,7 +551,7 @@ def _propfind_response(path, item, props, user, write=False):
else: else:
uri = "/" + posixpath.join(collection.path, item.href) uri = "/" + posixpath.join(collection.path, item.href)
href.text = _href(collection, uri) href.text = _href(base_prefix, uri)
response.append(href) response.append(href)
propstat404 = ET.Element(_tag("D", "propstat")) propstat404 = ET.Element(_tag("D", "propstat"))
@ -574,7 +573,7 @@ def _propfind_response(path, item, props, user, write=False):
element.text = item.last_modified element.text = item.last_modified
elif tag == _tag("D", "principal-collection-set"): elif tag == _tag("D", "principal-collection-set"):
tag = ET.Element(_tag("D", "href")) tag = ET.Element(_tag("D", "href"))
tag.text = _href(collection, "/") tag.text = _href(base_prefix, "/")
element.append(tag) element.append(tag)
elif (tag in (_tag("C", "calendar-user-address-set"), elif (tag in (_tag("C", "calendar-user-address-set"),
_tag("D", "principal-URL"), _tag("D", "principal-URL"),
@ -582,7 +581,7 @@ def _propfind_response(path, item, props, user, write=False):
_tag("C", "calendar-home-set")) and _tag("C", "calendar-home-set")) and
collection.is_principal and is_collection): collection.is_principal and is_collection):
tag = ET.Element(_tag("D", "href")) tag = ET.Element(_tag("D", "href"))
tag.text = _href(collection, path) tag.text = _href(base_prefix, path)
element.append(tag) element.append(tag)
elif tag == _tag("C", "supported-calendar-component-set"): elif tag == _tag("C", "supported-calendar-component-set"):
human_tag = _tag_from_clark(tag) human_tag = _tag_from_clark(tag)
@ -600,7 +599,7 @@ def _propfind_response(path, item, props, user, write=False):
is404 = True is404 = True
elif tag == _tag("D", "current-user-principal"): elif tag == _tag("D", "current-user-principal"):
tag = ET.Element(_tag("D", "href")) tag = ET.Element(_tag("D", "href"))
tag.text = _href(collection, ("/%s/" % user) if user else "/") tag.text = _href(base_prefix, ("/%s/" % user) if user else "/")
element.append(tag) element.append(tag)
elif tag == _tag("D", "current-user-privilege-set"): elif tag == _tag("D", "current-user-privilege-set"):
privilege = ET.Element(_tag("D", "privilege")) privilege = ET.Element(_tag("D", "privilege"))
@ -720,7 +719,7 @@ def _add_propstat_to(element, tag, status_number):
propstat.append(status) propstat.append(status)
def proppatch(path, xml_request, collection): def proppatch(base_prefix, path, xml_request, collection):
"""Read and answer PROPPATCH requests. """Read and answer PROPPATCH requests.
Read rfc4918-9.2 for info. Read rfc4918-9.2 for info.
@ -735,7 +734,7 @@ def proppatch(path, xml_request, collection):
multistatus.append(response) multistatus.append(response)
href = ET.Element(_tag("D", "href")) href = ET.Element(_tag("D", "href"))
href.text = _href(collection, path) href.text = _href(base_prefix, path)
response.append(href) response.append(href)
for short_name in props_to_remove: for short_name in props_to_remove:
@ -748,7 +747,7 @@ def proppatch(path, xml_request, collection):
return _pretty_xml(multistatus) return _pretty_xml(multistatus)
def report(path, xml_request, collection): def report(base_prefix, path, xml_request, collection):
"""Read and answer REPORT requests. """Read and answer REPORT requests.
Read rfc3253-3.6 for info. Read rfc3253-3.6 for info.
@ -765,12 +764,11 @@ def report(path, xml_request, collection):
_tag("C", "calendar-multiget"), _tag("C", "calendar-multiget"),
_tag("CR", "addressbook-multiget")): _tag("CR", "addressbook-multiget")):
# Read rfc4791-7.9 for info # Read rfc4791-7.9 for info
base_prefix = collection.configuration.get("server", "base_prefix")
hreferences = set() hreferences = set()
for href_element in root.findall(_tag("D", "href")): for href_element in root.findall(_tag("D", "href")):
href_path = unquote(urlparse(href_element.text).path) href_path = unquote(urlparse(href_element.text).path)
if href_path.startswith(base_prefix): if (href_path + "/").startswith(base_prefix + "/"):
hreferences.add(href_path[len(base_prefix) - 1:]) hreferences.add(href_path[len(base_prefix):])
else: else:
hreferences = (path,) hreferences = (path,)
filters = ( filters = (
@ -787,7 +785,8 @@ def report(path, xml_request, collection):
# Reference is an item # Reference is an item
item = collection.get(name) item = collection.get(name)
if not item: if not item:
response = _item_response(hreference, found_item=False) response = _item_response(base_prefix, hreference,
found_item=False)
multistatus.append(response) multistatus.append(response)
continue continue
items = [item] items = [item]
@ -828,13 +827,14 @@ def report(path, xml_request, collection):
uri = "/" + posixpath.join(collection.path, item.href) uri = "/" + posixpath.join(collection.path, item.href)
multistatus.append(_item_response( multistatus.append(_item_response(
uri, found_props=found_props, base_prefix, uri, found_props=found_props,
not_found_props=not_found_props, found_item=True)) not_found_props=not_found_props, found_item=True))
return _pretty_xml(multistatus) return _pretty_xml(multistatus)
def _item_response(href, found_props=(), not_found_props=(), found_item=True): def _item_response(base_prefix, href, found_props=(), not_found_props=(),
found_item=True):
response = ET.Element(_tag("D", "response")) response = ET.Element(_tag("D", "response"))
href_tag = ET.Element(_tag("D", "href")) href_tag = ET.Element(_tag("D", "href"))