diff --git a/django/utils/csp.py b/django/utils/csp.py index d57fc9899519..08a9d0752e38 100644 --- a/django/utils/csp.py +++ b/django/utils/csp.py @@ -71,15 +71,16 @@ class LazyNonce(SimpleLazyObject): """ def __init__(self): - super().__init__(self._generate) - - def _generate(self): - return secrets.token_urlsafe(16) + super().__init__(generate_nonce) def __bool__(self): return self._wrapped is not empty +def generate_nonce(): + return secrets.token_urlsafe(16) + + def build_policy(config, nonce=None): policy = [] diff --git a/django/utils/functional.py b/django/utils/functional.py index e26cbdfaa867..6c725e122148 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -407,7 +407,11 @@ def _setup(self): # without evaluating the wrapped object. def __repr__(self): if self._wrapped is empty: - repr_attr = self._setupfunc + # Avoid recursion if setupfunc is a bound method of this instance. + if getattr(self._setupfunc, "__self__", None) is self: + repr_attr = f"" + else: + repr_attr = self._setupfunc else: repr_attr = self._wrapped return "<%s: %r>" % (type(self).__name__, repr_attr) diff --git a/docs/releases/6.0.1.txt b/docs/releases/6.0.1.txt index 790fa76e997e..90158b6ff2aa 100644 --- a/docs/releases/6.0.1.txt +++ b/docs/releases/6.0.1.txt @@ -27,3 +27,7 @@ Bugfixes * Fixed a visual regression in Django 6.0 for admin form fields grouped under a ``
`` in Safari (:ticket:`36807`). + +* Fixed a crash in Django 6.0 caused by infinite recursion when calling + ``repr()`` on an unevaluated ``django.utils.csp.LazyNonce`` instance + (:ticket:`36810`). diff --git a/tests/utils_tests/test_csp.py b/tests/utils_tests/test_csp.py index 86682544e2f8..c737b59256ae 100644 --- a/tests/utils_tests/test_csp.py +++ b/tests/utils_tests/test_csp.py @@ -2,7 +2,7 @@ from unittest.mock import patch from django.test import SimpleTestCase -from django.utils.csp import CSP, LazyNonce, build_policy +from django.utils.csp import CSP, LazyNonce, build_policy, generate_nonce from django.utils.functional import empty basic_config = { @@ -155,6 +155,7 @@ def memento_token_urlsafe(size): self.assertTrue(nonce) self.assertEqual(nonce, val) self.assertIsInstance(nonce, str) + self.assertEqual(repr(nonce), f"") self.assertEqual(len(val), 22) # Based on secrets.token_urlsafe of 16 bytes. self.assertEqual(generated_tokens, [nonce]) # Also test the wrapped value. @@ -166,3 +167,10 @@ def test_returns_same_value(self): second = str(nonce) self.assertEqual(first, second) + + def test_repr(self): + nonce = LazyNonce() + self.assertEqual(repr(nonce), f"") + + str(nonce) # Force nonce generation. + self.assertRegex(repr(nonce), r"") diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index a95335413e64..e538c6637723 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -348,6 +348,22 @@ def test_repr(self): self.assertIsInstance(obj._wrapped, int) self.assertEqual(repr(obj), "") + def test_repr_bound_method(self): + + class MyLazyGenerator(SimpleLazyObject): + def __init__(self): + super().__init__(self._generate) + + def _generate(self): + return "test-generated-value" + + obj = MyLazyGenerator() + self.assertEqual(repr(obj), "'>") + self.assertIs(obj._wrapped, empty) # The evaluation hasn't happened. + + self.assertEqual(str(obj), "test-generated-value") # Evaluate. + self.assertEqual(repr(obj), "") + def test_add(self): obj1 = self.lazy_wrap(1) self.assertEqual(obj1 + 1, 2)