From 0c600a6ce6d088cafab3aafc85570335bc1adfa2 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 4 Apr 2024 16:12:47 -0500 Subject: [PATCH] Redirects: fix root redirect (/ -> ) (#11265) The rstrip call was removing the whole path for `/`, we need to preserve the leading `/` in the path. --- .../proxito/tests/test_old_redirects.py | 45 +++++++++++++++++++ readthedocs/redirects/querysets.py | 13 +++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/readthedocs/proxito/tests/test_old_redirects.py b/readthedocs/proxito/tests/test_old_redirects.py index a1604668e82..d52c5bdd8b0 100644 --- a/readthedocs/proxito/tests/test_old_redirects.py +++ b/readthedocs/proxito/tests/test_old_redirects.py @@ -1243,6 +1243,51 @@ def test_redirect_with_301_status(self): "http://project.dev.readthedocs.io/en/latest/tutorial/install.html", ) + def test_page_root_redirect(self): + fixture.get( + Redirect, + project=self.project, + redirect_type=PAGE_REDIRECT, + from_url="/", + to_url="https://example.com/", + force=True, + ) + r = self.client.get( + "/en/latest/", headers={"host": "project.dev.readthedocs.io"} + ) + self.assertEqual(r.status_code, 302) + self.assertEqual(r["Location"], "https://example.com/") + + def test_exact_root_redirect_single_version(self): + self.project.versioning_scheme = SINGLE_VERSION_WITHOUT_TRANSLATIONS + self.project.save() + fixture.get( + Redirect, + project=self.project, + redirect_type=EXACT_REDIRECT, + from_url="/", + to_url="https://example.com/", + force=True, + ) + r = self.client.get("/", headers={"host": "project.dev.readthedocs.io"}) + self.assertEqual(r.status_code, 302) + self.assertEqual(r["Location"], "https://example.com/") + + def test_page_root_redirect_single_version(self): + self.project.versioning_scheme = SINGLE_VERSION_WITHOUT_TRANSLATIONS + self.project.save() + fixture.get( + Redirect, + project=self.project, + redirect_type=PAGE_REDIRECT, + from_url="/", + to_url="https://example.com/", + force=True, + ) + r = self.client.get("/", headers={"host": "project.dev.readthedocs.io"}) + self.assertEqual(r.status_code, 302) + self.assertEqual(r["Location"], "https://example.com/") + @override_settings( PYTHON_MEDIA=True, diff --git a/readthedocs/redirects/querysets.py b/readthedocs/redirects/querysets.py index 54b2e942a59..9b7fd6cd77b 100644 --- a/readthedocs/redirects/querysets.py +++ b/readthedocs/redirects/querysets.py @@ -60,8 +60,10 @@ def get_matching_redirect_with_path( # Useful to allow redirects to match paths with or without trailling slash. # For example, ``/docs`` will match ``/docs/`` and ``/docs``. - filename_without_trailling_slash = normalized_filename.rstrip("/") - path_without_trailling_slash = normalized_path.rstrip("/") + filename_without_trailling_slash = self._strip_trailling_slash( + normalized_filename + ) + path_without_trailling_slash = self._strip_trailling_slash(normalized_path) # Add extra fields with the ``filename`` and ``path`` to perform a # filter at db level instead with Python. @@ -154,3 +156,10 @@ def _normalize_path(self, path): normalized_path = parsed_path._replace(query="").geturl() normalized_path = "/" + normalized_path.lstrip("/") return normalized_path + + def _strip_trailling_slash(self, path): + """Stripe the trailling slash from the path, making sure the root path is always ``/``.""" + path = path.rstrip("/") + if path == "": + return "/" + return path