diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index 0bcded6d..98123cd6 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -204,7 +204,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead, "%s", environ.get("REQUEST_METHOD", "unknown"), environ.get("PATH_INFO", ""), e, exc_info=True) # Make minimal response - status, raw_headers, raw_answer = ( + status, raw_headers, raw_answer, xml_request = ( httputils.INTERNAL_SERVER_ERROR) assert isinstance(raw_answer, str) answer = raw_answer.encode("ascii") @@ -224,12 +224,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead, unsafe_path = environ.get("PATH_INFO", "") https = environ.get("HTTPS", "") profiler = None + xml_request = None context = AuthContext() """Manage a request.""" def response(status: int, headers: types.WSGIResponseHeaders, - answer: Union[None, str, bytes]) -> _IntermediateResponse: + answer: Union[None, str, bytes], + xml_request: Union[None, str] = None) -> _IntermediateResponse: """Helper to create response from internal types.WSGIResponse""" headers = dict(headers) content_encoding = "plain" @@ -468,7 +470,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead, elif self._profiling_per_request_method: self.profiler_per_request_method[request_method].enable() - status, headers, answer = function( + status, headers, answer, xml_request = function( environ, base_prefix, path, user, remote_host, remote_useragent) # Profiling @@ -478,13 +480,13 @@ class Application(ApplicationPartDelete, ApplicationPartHead, elif self._profiling_per_request_method: self.profiler_per_request_method[request_method].disable() - if (status, headers, answer) == httputils.NOT_ALLOWED: + if (status, headers, answer, xml_request) == httputils.NOT_ALLOWED: logger.info("Access to %r denied for %s", path, repr(user) if user else "anonymous user") else: - status, headers, answer = httputils.NOT_ALLOWED + status, headers, answer, xml_request = httputils.NOT_ALLOWED - if ((status, headers, answer) == httputils.NOT_ALLOWED and not user and + if ((status, headers, answer, xml_request) == httputils.NOT_ALLOWED and not user and not external_login): # Unknown or unauthorized user logger.debug("Asking client for authentication") @@ -494,4 +496,4 @@ class Application(ApplicationPartDelete, ApplicationPartHead, "WWW-Authenticate": "Basic realm=\"%s\"" % self._auth_realm}) - return response(status, headers, answer) + return response(status, headers, answer, xml_request) diff --git a/radicale/app/base.py b/radicale/app/base.py index 229f0f65..18a68b95 100644 --- a/radicale/app/base.py +++ b/radicale/app/base.py @@ -93,7 +93,7 @@ class ApplicationBase: """Generate XML error response.""" headers = {"Content-Type": "text/xml; charset=%s" % self._encoding} content = self._xml_response(xmlutils.webdav_error(human_tag)) - return status, headers, content + return status, headers, content, None class Access: diff --git a/radicale/app/delete.py b/radicale/app/delete.py index 61fe6c43..2201e998 100644 --- a/radicale/app/delete.py +++ b/radicale/app/delete.py @@ -110,4 +110,4 @@ class ApplicationPartDelete(ApplicationBase): for notification_item in hook_notification_item_list: self._hook.notify(notification_item) headers = {"Content-Type": "text/xml; charset=%s" % self._encoding} - return client.OK, headers, self._xml_response(xml_answer) + return client.OK, headers, self._xml_response(xml_answer), None diff --git a/radicale/app/get.py b/radicale/app/get.py index b8adb39a..2eac58f1 100644 --- a/radicale/app/get.py +++ b/radicale/app/get.py @@ -109,4 +109,4 @@ class ApplicationPartGet(ApplicationBase): if content_disposition: headers["Content-Disposition"] = content_disposition answer = item.serialize() - return client.OK, headers, answer + return client.OK, headers, answer, None diff --git a/radicale/app/mkcalendar.py b/radicale/app/mkcalendar.py index db14bfdc..53abcdbd 100644 --- a/radicale/app/mkcalendar.py +++ b/radicale/app/mkcalendar.py @@ -89,4 +89,4 @@ class ApplicationPartMkcalendar(ApplicationBase): logger.warning( "Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) return httputils.BAD_REQUEST - return client.CREATED, {}, None + return client.CREATED, {}, None, xmlutils.pretty_xml(xml_content) diff --git a/radicale/app/mkcol.py b/radicale/app/mkcol.py index 72d5aa2b..45ad7c4a 100644 --- a/radicale/app/mkcol.py +++ b/radicale/app/mkcol.py @@ -94,4 +94,4 @@ class ApplicationPartMkcol(ApplicationBase): "Bad MKCOL request on %r (type:%s): %s", path, collection_type, e, exc_info=True) return httputils.BAD_REQUEST logger.info("MKCOL request %r (type:%s): %s", path, collection_type, "successful") - return client.CREATED, {}, None + return client.CREATED, {}, None, xmlutils.pretty_xml(xml_content) diff --git a/radicale/app/move.py b/radicale/app/move.py index ac4fcab4..b65f6600 100644 --- a/radicale/app/move.py +++ b/radicale/app/move.py @@ -127,4 +127,4 @@ class ApplicationPartMove(ApplicationBase): logger.warning( "Bad MOVE request on %r: %s", path, e, exc_info=True) return httputils.BAD_REQUEST - return client.NO_CONTENT if to_item else client.CREATED, {}, None + return client.NO_CONTENT if to_item else client.CREATED, {}, None, None diff --git a/radicale/app/options.py b/radicale/app/options.py index a869e2ab..9e347de2 100644 --- a/radicale/app/options.py +++ b/radicale/app/options.py @@ -33,4 +33,4 @@ class ApplicationPartOptions(ApplicationBase): "Allow": ", ".join( name[3:] for name in dir(self) if name.startswith("do_")), "DAV": httputils.DAV_HEADERS} - return client.OK, headers, None + return client.OK, headers, None, None diff --git a/radicale/app/propfind.py b/radicale/app/propfind.py index b546c5e1..d79d0269 100644 --- a/radicale/app/propfind.py +++ b/radicale/app/propfind.py @@ -410,4 +410,4 @@ class ApplicationPartPropfind(ApplicationBase): allowed_items, user, self._encoding) if xml_answer is None: return httputils.NOT_ALLOWED - return client.MULTI_STATUS, headers, self._xml_response(xml_answer) + return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index 9d6dc221..caaf7b7a 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -131,4 +131,4 @@ class ApplicationPartProppatch(ApplicationBase): logger.warning( "Bad PROPPATCH request on %r: %s", path, e, exc_info=True) return httputils.BAD_REQUEST - return client.MULTI_STATUS, headers, self._xml_response(xml_answer) + return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content) diff --git a/radicale/app/put.py b/radicale/app/put.py index 89a401a8..bd049158 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -334,7 +334,7 @@ class ApplicationPartPut(ApplicationBase): if (item and item.uid == prepared_item.uid): logger.debug("PUT request updated existing item %r", path) headers = {"ETag": etag} - return client.NO_CONTENT, headers, None + return client.NO_CONTENT, headers, None, None headers = {"ETag": etag} - return client.CREATED, headers, None + return client.CREATED, headers, None, None diff --git a/radicale/app/report.py b/radicale/app/report.py index dda4ba10..2bad1924 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -815,7 +815,7 @@ class ApplicationPartReport(ApplicationBase): "Bad REPORT request on %r: %s", path, e, exc_info=True) return httputils.BAD_REQUEST headers = {"Content-Type": "text/calendar; charset=%s" % self._encoding} - return status, headers, str(body) + return status, headers, str(body), xmlutils.pretty_xml(xml_content) else: try: status, xml_answer = xml_report( @@ -826,4 +826,4 @@ class ApplicationPartReport(ApplicationBase): "Bad REPORT request on %r: %s", path, e, exc_info=True) return httputils.BAD_REQUEST headers = {"Content-Type": "text/xml; charset=%s" % self._encoding} - return status, headers, self._xml_response(xml_answer) + return status, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content) diff --git a/radicale/httputils.py b/radicale/httputils.py index d97d0513..30c6a7a0 100644 --- a/radicale/httputils.py +++ b/radicale/httputils.py @@ -49,42 +49,42 @@ else: NOT_ALLOWED: types.WSGIResponse = ( client.FORBIDDEN, (("Content-Type", "text/plain"),), - "Access to the requested resource forbidden.") + "Access to the requested resource forbidden.", None) FORBIDDEN: types.WSGIResponse = ( client.FORBIDDEN, (("Content-Type", "text/plain"),), - "Action on the requested resource refused.") + "Action on the requested resource refused.", None) BAD_REQUEST: types.WSGIResponse = ( - client.BAD_REQUEST, (("Content-Type", "text/plain"),), "Bad Request") + client.BAD_REQUEST, (("Content-Type", "text/plain"),), "Bad Request", None) NOT_FOUND: types.WSGIResponse = ( client.NOT_FOUND, (("Content-Type", "text/plain"),), - "The requested resource could not be found.") + "The requested resource could not be found.", None) CONFLICT: types.WSGIResponse = ( client.CONFLICT, (("Content-Type", "text/plain"),), - "Conflict in the request.") + "Conflict in the request.", None) METHOD_NOT_ALLOWED: types.WSGIResponse = ( client.METHOD_NOT_ALLOWED, (("Content-Type", "text/plain"),), - "The method is not allowed on the requested resource.") + "The method is not allowed on the requested resource.", None) PRECONDITION_FAILED: types.WSGIResponse = ( client.PRECONDITION_FAILED, - (("Content-Type", "text/plain"),), "Precondition failed.") + (("Content-Type", "text/plain"),), "Precondition failed.", None) REQUEST_TIMEOUT: types.WSGIResponse = ( client.REQUEST_TIMEOUT, (("Content-Type", "text/plain"),), - "Connection timed out.") + "Connection timed out.", None) REQUEST_ENTITY_TOO_LARGE: types.WSGIResponse = ( client.REQUEST_ENTITY_TOO_LARGE, (("Content-Type", "text/plain"),), - "Request body too large.") + "Request body too large.", None) REMOTE_DESTINATION: types.WSGIResponse = ( client.BAD_GATEWAY, (("Content-Type", "text/plain"),), - "Remote destination not supported.") + "Remote destination not supported.", None) DIRECTORY_LISTING: types.WSGIResponse = ( client.FORBIDDEN, (("Content-Type", "text/plain"),), - "Directory listings are not supported.") + "Directory listings are not supported.", None) INSUFFICIENT_STORAGE: types.WSGIResponse = ( client.INSUFFICIENT_STORAGE, (("Content-Type", "text/plain"),), - "Insufficient Storage. Please contact the administrator.") + "Insufficient Storage. Please contact the administrator.", None) INTERNAL_SERVER_ERROR: types.WSGIResponse = ( client.INTERNAL_SERVER_ERROR, (("Content-Type", "text/plain"),), - "A server error occurred. Please contact the administrator.") + "A server error occurred. Please contact the administrator.", None) DAV_HEADERS: str = "1, 2, 3, calendar-access, addressbook, extended-mkcol" @@ -159,7 +159,7 @@ def read_request_body(configuration: "config.Configuration", def redirect(location: str, status: int = client.FOUND) -> types.WSGIResponse: return (status, {"Location": location, "Content-Type": "text/plain"}, - "Redirected to %s" % location) + "Redirected to %s" % location, None) def _serve_traversable( @@ -214,7 +214,7 @@ def _serve_traversable( # adjust on the fly default main.js of InfCloud installation logger.debug("Adjust on-the-fly default InfCloud main.js in served page: %r", path) answer = answer.replace(b"'InfCloud - the open source CalDAV/CardDAV web client'", b"'InfCloud - the open source CalDAV/CardDAV web client - served through Radicale CalDAV/CardDAV server'") - return client.OK, headers, answer + return client.OK, headers, answer, None def serve_resource( diff --git a/radicale/tests/custom/web.py b/radicale/tests/custom/web.py index 695bbe81..2570fec0 100644 --- a/radicale/tests/custom/web.py +++ b/radicale/tests/custom/web.py @@ -28,9 +28,9 @@ class Web(web.BaseWeb): def get(self, environ: types.WSGIEnviron, base_prefix: str, path: str, user: str) -> types.WSGIResponse: - return client.OK, {"Content-Type": "text/plain"}, "custom" + return client.OK, {"Content-Type": "text/plain"}, "custom", None def post(self, environ: types.WSGIEnviron, base_prefix: str, path: str, user: str) -> types.WSGIResponse: content = httputils.read_request_body(self.configuration, environ) - return client.OK, {"Content-Type": "text/plain"}, "echo:" + content + return client.OK, {"Content-Type": "text/plain"}, "echo:" + content, None diff --git a/radicale/types.py b/radicale/types.py index 6899a755..6c7a7198 100644 --- a/radicale/types.py +++ b/radicale/types.py @@ -20,7 +20,7 @@ from typing import (Any, Callable, ContextManager, Iterator, List, Mapping, runtime_checkable) WSGIResponseHeaders = Union[Mapping[str, str], Sequence[Tuple[str, str]]] -WSGIResponse = Tuple[int, WSGIResponseHeaders, Union[None, str, bytes]] +WSGIResponse = Tuple[int, WSGIResponseHeaders, Union[None, str, bytes], Union[None, str]] WSGIEnviron = Mapping[str, Any] WSGIStartResponse = Callable[[str, List[Tuple[str, str]]], Any] diff --git a/radicale/web/none.py b/radicale/web/none.py index 263992ec..15e255c7 100644 --- a/radicale/web/none.py +++ b/radicale/web/none.py @@ -32,4 +32,4 @@ class Web(web.BaseWeb): assert pathutils.sanitize_path(path) == path if path != "/.web": return httputils.redirect(base_prefix + "/.web") - return client.OK, {"Content-Type": "text/plain"}, "Radicale works!" + return client.OK, {"Content-Type": "text/plain"}, "Radicale works!", None