From ed13a58bf63df94508c8a0fe779da0b6a2bc26bb Mon Sep 17 00:00:00 2001 From: kasey Date: Tue, 12 May 2026 00:15:15 -0500 Subject: [PATCH 1/2] Fixed #37096 -- Fixed test_invalid_choice_db_option on Python 3.14.5+. --- AUTHORS | 1 + tests/admin_scripts/tests.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a752a2e0e892..2c8d5a9845ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -598,6 +598,7 @@ answer newbie questions, and generally made Django that much better: Karderio Karen Tracey Karol Sikora + Kasey Steinhauer Kasun Herath Katherine “Kati” Michel Kathryn Killebrew diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 114d819847dc..914f54720ce5 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -2450,7 +2450,8 @@ def test_invalid_choice_db_option(self): if PY314: expected_error = ( r"Error: argument --database: invalid choice: 'deflaut', " - r"maybe you meant 'default'\? \(choose from default, other\)" + r"maybe you meant 'default'\? " + r"\(choose from '?default'?, '?other'?\)" ) else: expected_error = ( From 335c6d0129400eda792f3bec5c71bb28af5e5d37 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 12 May 2026 15:27:59 -0400 Subject: [PATCH 2/2] Fixed #37095 -- Checked maximum redirect lengths against percent-encoded URLs. --- django/http/response.py | 5 ++--- tests/httpwrappers/tests.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/django/http/response.py b/django/http/response.py index 45fb0177d1e3..17b8da4da179 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -642,12 +642,11 @@ def __init__( ): super().__init__(*args, **kwargs) self["Location"] = iri_to_uri(redirect_to) - redirect_to_str = str(redirect_to) - if max_length is not None and len(redirect_to_str) > max_length: + if max_length is not None and len(self["Location"]) > max_length: raise DisallowedRedirect( f"Unsafe redirect exceeding {max_length} characters" ) - parsed = urlsplit(redirect_to_str) + parsed = urlsplit(str(redirect_to)) if preserve_request: self.status_code = self.status_code_preserve_request if parsed.scheme and parsed.scheme not in self.allowed_schemes: diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index b990e9f81656..151088909c59 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -24,6 +24,7 @@ parse_cookie, ) from django.test import SimpleTestCase +from django.utils.encoding import iri_to_uri from django.utils.functional import lazystr from django.utils.http import MAX_URL_REDIRECT_LENGTH @@ -498,6 +499,18 @@ def test_redirect_url_max_length(self): response = response_class(long_url) self.assertEqual(response.url, long_url) + def test_redirect_url_max_length_checks_encoded_location(self): + long_url = "/" + "é" * (MAX_URL_REDIRECT_LENGTH - 1) + self.assertLessEqual(len(long_url), MAX_URL_REDIRECT_LENGTH) + self.assertGreater(len(iri_to_uri(long_url)), MAX_URL_REDIRECT_LENGTH) + for response_class in (HttpResponseRedirect, HttpResponsePermanentRedirect): + msg = f"Unsafe redirect exceeding {MAX_URL_REDIRECT_LENGTH} characters" + with ( + self.subTest(response_class=response_class), + self.assertRaisesMessage(DisallowedRedirect, msg), + ): + response_class(long_url) + def test_redirect_url_max_length_override_via_param(self): base_url = "https://example.com/" for (max_length, length), response_class in itertools.product(