diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index f1233e0..18d9808 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -209,8 +209,15 @@ class Application(ApplicationPartDelete, ApplicationPartHead, if not function: return response(*httputils.METHOD_NOT_ALLOWED) - # If "/.well-known" is not available, clients query "/" - if path == "/.well-known" or path.startswith("/.well-known/"): + # Redirect all "…/.well-known/{caldav,carddav}" paths to "/". + # This shouldn't be necessary but some clients like TbSync require it. + # Status must be MOVED PERMANENTLY using FOUND causes problems + if (path.rstrip("/").endswith("/.well-known/caldav") or + path.rstrip("/").endswith("/.well-known/carddav")): + return response(*httputils.redirect( + base_prefix + "/", client.MOVED_PERMANENTLY)) + # Return NOT FOUND for all other paths containing ".well-knwon" + if path.endswith("/.well-known") or "/.well-known/" in path: return response(*httputils.NOT_FOUND) # Ask authentication backend to check rights diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 03e2f63..f3d6fdb 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -1615,14 +1615,31 @@ permissions: RrWw""") self.delete("/") self.propfind("/") + def test_well_known(self) -> None: + for path in ["/.well-known/caldav", "/.well-known/carddav"]: + for path in [path, "/foo" + path]: + _, headers, _ = self.request("GET", path, check=301) + assert headers.get("Location") == "/" + + def test_well_known_script_name(self) -> None: + for path in ["/.well-known/caldav", "/.well-known/carddav"]: + for path in [path, "/foo" + path]: + _, headers, _ = self.request( + "GET", path, check=301, SCRIPT_NAME="/radicale") + assert headers.get("Location") == "/radicale/" + + def test_well_known_not_found(self) -> None: + for path in ["/.well-known", "/.well-known/", "/.well-known/foo"]: + for path in [path, "/foo" + path]: + self.get(path, check=404) + def test_custom_headers(self) -> None: self.configure({"headers": {"test": "123"}}) # Test if header is set on success _, headers, _ = self.request("OPTIONS", "/", check=200) assert headers.get("test") == "123" # Test if header is set on failure - _, headers, _ = self.request("GET", "/.well-known/does not exist", - check=404) + _, headers, _ = self.request("GET", "/.well-known/foo", check=404) assert headers.get("test") == "123" @pytest.mark.skipif(sys.version_info < (3, 6),