Fix setlang redirect normalization for locale variants

This commit is contained in:
2026-04-11 20:48:55 +02:00
parent 58139b08ff
commit 605f1e8276
3 changed files with 33 additions and 6 deletions

View File

@@ -21,11 +21,17 @@ def normalize_set_language_next(value: str | None) -> str:
if not path.startswith("/"): if not path.startswith("/"):
path = f"/{path}" path = f"/{path}"
language_codes = [code for code, _ in settings.LANGUAGES] configured_codes = {
if language_codes: str(code).lower().replace("_", "-") for code, _ in settings.LANGUAGES
pattern = ( }
rf"^/(?:{'|'.join(re.escape(code) for code in language_codes)})(?=/|$)" first_segment, _, remainder = path.lstrip("/").partition("/")
) normalized_segment = first_segment.lower().replace("_", "-")
path = re.sub(pattern, "", path, count=1) or "/" looks_like_language_code = bool(
re.fullmatch(r"[a-z]{2}(?:-[a-z]{2})?", normalized_segment)
)
should_strip = normalized_segment in configured_codes or looks_like_language_code
if should_strip:
path = f"/{remainder}" if remainder else "/"
return urlunsplit(("", "", path, parsed.query, "")) return urlunsplit(("", "", path, parsed.query, ""))

View File

@@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.i18n import set_language as django_set_language from django.views.i18n import set_language as django_set_language
from .i18n_utils import normalize_set_language_next from .i18n_utils import normalize_set_language_next
@csrf_exempt
def set_language_normalized(request: HttpRequest) -> HttpResponse: def set_language_normalized(request: HttpRequest) -> HttpResponse:
"""Normalize `next` before delegating to Django's set_language view.""" """Normalize `next` before delegating to Django's set_language view."""
if request.method == "POST": if request.method == "POST":

View File

@@ -29,6 +29,12 @@ class SetLanguageNormalizationTests(SimpleTestCase):
"/manage/checkout/paymentmethod/", "/manage/checkout/paymentmethod/",
) )
def test_normalize_set_language_next_strips_locale_variant_prefix(self):
self.assertEqual(
normalize_set_language_next("/en-us/manage/checkout/paymentmethod/"),
"/manage/checkout/paymentmethod/",
)
@patch("mandelstudio.i18n_views.django_set_language") @patch("mandelstudio.i18n_views.django_set_language")
def test_post_next_is_normalized_before_delegate(self, django_set_language): def test_post_next_is_normalized_before_delegate(self, django_set_language):
django_set_language.side_effect = lambda request: HttpResponseRedirect( django_set_language.side_effect = lambda request: HttpResponseRedirect(
@@ -61,3 +67,16 @@ class SetLanguageNormalizationTests(SimpleTestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(django_set_language.call_args.args[0].GET["next"], "/manage/") self.assertEqual(django_set_language.call_args.args[0].GET["next"], "/manage/")
@patch("mandelstudio.i18n_views.django_set_language")
def test_set_language_view_is_csrf_exempt(self, django_set_language):
django_set_language.return_value = HttpResponseRedirect("/manage/")
request = self.factory.post(
"/i18n/setlang/",
data={"language": "nl", "next": "/en/manage/"},
)
request.csrf_processing_done = False
response = set_language_normalized(request)
self.assertEqual(response.status_code, 302)