diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index ca8594ec..aab4beb5 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -193,12 +193,6 @@ class Application(ApplicationPartDelete, ApplicationPartHead, # Sanitize request URI (a WSGI server indicates with an empty path, # that the URL targets the application root without a trailing slash) path = pathutils.sanitize_path(unsafe_path) - if unsafe_path != path and request_method in ["GET", "HEAD"]: - location = base_prefix + path - logger.info("Redirecting to sanitized path: %r ==> %r", - base_prefix + unsafe_path, location) - return response(*httputils.redirect( - location, client.MOVED_PERMANENTLY)) logger.debug("Sanitized path: %r", path) # Get function corresponding to method diff --git a/radicale/app/get.py b/radicale/app/get.py index ce953244..7e5feeb4 100644 --- a/radicale/app/get.py +++ b/radicale/app/get.py @@ -60,11 +60,18 @@ class ApplicationPartGet(ApplicationBase): def do_GET(self, environ: types.WSGIEnviron, base_prefix: str, path: str, user: str) -> types.WSGIResponse: """Manage GET request.""" - # Redirect to .web if the root URL is requested + # Redirect to /.web if the root path is requested if not pathutils.strip_path(path): - return httputils.redirect(".web") - # Dispatch .web URL to web module + return httputils.redirect(base_prefix + "/.web") if path == "/.web" or path.startswith("/.web/"): + # Redirect to sanitized path for all subpaths of /.web + unsafe_path = environ.get("PATH_INFO", "") + if unsafe_path != path: + location = base_prefix + path + logger.info("Redirecting to sanitized path: %r ==> %r", + base_prefix + unsafe_path, location) + return httputils.redirect(location, client.MOVED_PERMANENTLY) + # Dispatch /.web path to web module return self._web.get(environ, base_prefix, path, user) access = Access(self._rights, user, path) if not access.check("r") and "i" not in access.permissions: diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 17e4791b..1b8afd98 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -53,26 +53,28 @@ permissions: RrWw""") def test_root(self) -> None: """GET request at "/".""" - status, headers, answer = self.request("GET", "/", check=302) - assert headers.get("Location") == ".web" - assert answer == "Redirected to .web" + for path in ["", "/", "//"]: + _, headers, answer = self.request("GET", path, check=302) + assert headers.get("Location") == "/.web" + assert answer == "Redirected to /.web" def test_root_script_name(self) -> None: """GET request at "/" with SCRIPT_NAME.""" - _, answer = self.get("/", check=302, SCRIPT_NAME="/radicale") - assert answer == "Redirected to .web" + for path in ["", "/", "//"]: + _, headers, _ = self.request("GET", path, check=302, + SCRIPT_NAME="/radicale") + assert headers.get("Location") == "/radicale/.web" def test_sanitized_path(self) -> None: """GET request with unsanitized paths.""" - for path, sane_path in [("//", "/"), ("", "/"), ("/a//b", "/a/b"), - ("/a//b/", "/a/b/")]: - _, headers, answer = self.request("GET", path, check=301) + for path, sane_path in [ + ("//.web", "/.web"), ("//.web/", "/.web/"), + ("/.web//", "/.web/"), ("/.web/a//b", "/.web/a/b")]: + _, headers, _ = self.request("GET", path, check=301) assert headers.get("Location") == sane_path - assert answer == "Redirected to %s" % sane_path - _, headers, answer = self.request("GET", path, check=301, - SCRIPT_NAME="/radicale") + _, headers, _ = self.request("GET", path, check=301, + SCRIPT_NAME="/radicale") assert headers.get("Location") == "/radicale%s" % sane_path - assert answer == "Redirected to /radicale%s" % sane_path def test_add_event(self) -> None: """Add an event."""