Version in base suite: 3.2.19-1+deb12u1
Base version: python-django_3.2.19-1+deb12u1
Target version: python-django_3.2.19-1+deb12u2
Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-django/python-django_3.2.19-1+deb12u1.dsc
Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-django/python-django_3.2.19-1+deb12u2.dsc
changelog | 23 +++
patches/0014-CVE-2023-36053.patch | 242 ++++++++++++++++++++++++++++++++++++
patches/0015-CVE-2024-39329.patch | 80 +++++++++++
patches/0016-CVE-2024-39330.patch | 145 +++++++++++++++++++++
patches/0017-CVE-2024-39614-1.patch | 127 ++++++++++++++++++
patches/0018-CVE-2024-39614-2.patch | 92 +++++++++++++
patches/0019-CVE-2024-41989.patch | 67 +++++++++
patches/0020-CVE-2024-41991.patch | 108 ++++++++++++++++
patches/0021-CVE-2024-42005.patch | 70 ++++++++++
patches/CVE-2023-36053.patch | 242 ------------------------------------
patches/series | 9 +
11 files changed, 962 insertions(+), 243 deletions(-)
diff -Nru python-django-3.2.19/debian/changelog python-django-3.2.19/debian/changelog
--- python-django-3.2.19/debian/changelog 2023-07-28 13:24:04.000000000 +0000
+++ python-django-3.2.19/debian/changelog 2024-08-21 11:08:24.000000000 +0000
@@ -1,3 +1,26 @@
+python-django (3:3.2.19-1+deb12u2) bookworm; urgency=high
+
+ * Rename CVE-2023-36053.patch to 0014-CVE-2023-36053.patch
+ * Backport upstream fixes in 3:4.2.14-1:
+ * Closes: #1076069
+ * CVE-2024-39329: Standardize timing of verify_password() when
+ checking unusable passwords.
+ * CVE-2024-39330: Add extra file name validation in Storage's save
+ method.
+ * CVE-2024-39614: Mitigate potential DoS in
+ get_supported_language_variant.
+ * The patch for CVE-2024-38875 won't sensibly backport.
+ * Backport upstream fixes in 3:4.2.15-1:
+ * Closes: #1078074
+ * CVE-2024-41989: Prevent excessive memory consumption in floatformat.
+ * CVE-2024-41991: Prevente potential ReDoS in django.utils.html.urlize()
+ and AdminURLFieldWidget.
+ * CVE-2024-42005: Mitigate QuerySet.values() SQL injection attacks against JSON fields
+ Backport and tweak the upstream fix series to fit into 3.2.
+ * The patch for CVE-2024-41990 won't sensibly backport.
+
+ -- Steve McIntyre <93sam@debian.org> Wed, 21 Aug 2024 12:08:24 +0100
+
python-django (3:3.2.19-1+deb12u1) bookworm-security; urgency=high
* CVE-2023-36053: Potential regular expression denial of service
diff -Nru python-django-3.2.19/debian/patches/0014-CVE-2023-36053.patch python-django-3.2.19/debian/patches/0014-CVE-2023-36053.patch
--- python-django-3.2.19/debian/patches/0014-CVE-2023-36053.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0014-CVE-2023-36053.patch 2024-08-07 15:56:53.000000000 +0000
@@ -0,0 +1,242 @@
+From: Mariusz Felisiak
+Date: Wed, 14 Jun 2023 12:23:06 +0200
+Subject: [PATCH] [3.2.x] Fixed CVE-2023-36053 -- Prevented potential ReDoS in
+ EmailValidator and URLValidator.
+
+Thanks Seokchan Yoon for reports.
+---
+ django/core/validators.py | 7 +++++--
+ django/forms/fields.py | 3 +++
+ docs/ref/forms/fields.txt | 7 ++++++-
+ docs/ref/validators.txt | 25 +++++++++++++++++++++++-
+ tests/forms_tests/field_tests/test_emailfield.py | 5 ++++-
+ tests/forms_tests/tests/test_forms.py | 19 ++++++++++++------
+ tests/validators/tests.py | 11 +++++++++++
+ 7 files changed, 66 insertions(+), 11 deletions(-)
+
+diff --git a/django/core/validators.py b/django/core/validators.py
+index 6b28eef08dd2..52ebddac6345 100644
+--- a/django/core/validators.py
++++ b/django/core/validators.py
+@@ -93,6 +93,7 @@ class URLValidator(RegexValidator):
+ message = _('Enter a valid URL.')
+ schemes = ['http', 'https', 'ftp', 'ftps']
+ unsafe_chars = frozenset('\t\r\n')
++ max_length = 2048
+
+ def __init__(self, schemes=None, **kwargs):
+ super().__init__(**kwargs)
+@@ -100,7 +101,7 @@ class URLValidator(RegexValidator):
+ self.schemes = schemes
+
+ def __call__(self, value):
+- if not isinstance(value, str):
++ if not isinstance(value, str) or len(value) > self.max_length:
+ raise ValidationError(self.message, code=self.code, params={'value': value})
+ if self.unsafe_chars.intersection(value):
+ raise ValidationError(self.message, code=self.code, params={'value': value})
+@@ -211,7 +212,9 @@ class EmailValidator:
+ self.domain_allowlist = allowlist
+
+ def __call__(self, value):
+- if not value or '@' not in value:
++ # The maximum length of an email is 320 characters per RFC 3696
++ # section 3.
++ if not value or '@' not in value or len(value) > 320:
+ raise ValidationError(self.message, code=self.code, params={'value': value})
+
+ user_part, domain_part = value.rsplit('@', 1)
+diff --git a/django/forms/fields.py b/django/forms/fields.py
+index 0214d60c1cf1..8adb09e38294 100644
+--- a/django/forms/fields.py
++++ b/django/forms/fields.py
+@@ -540,6 +540,9 @@ class EmailField(CharField):
+ default_validators = [validators.validate_email]
+
+ def __init__(self, **kwargs):
++ # The default maximum length of an email is 320 characters per RFC 3696
++ # section 3.
++ kwargs.setdefault("max_length", 320)
+ super().__init__(strip=True, **kwargs)
+
+
+diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
+index 9438214a28ce..5b485f215384 100644
+--- a/docs/ref/forms/fields.txt
++++ b/docs/ref/forms/fields.txt
+@@ -592,7 +592,12 @@ For each field, we describe the default widget used if you don't specify
+ * Error message keys: ``required``, ``invalid``
+
+ Has three optional arguments ``max_length``, ``min_length``, and
+- ``empty_value`` which work just as they do for :class:`CharField`.
++ ``empty_value`` which work just as they do for :class:`CharField`. The
++ ``max_length`` argument defaults to 320 (see :rfc:`3696#section-3`).
++
++ .. versionchanged:: 3.2.20
++
++ The default value for ``max_length`` was changed to 320 characters.
+
+ ``FileField``
+ -------------
+diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt
+index 50761e5a425c..b22762b17b93 100644
+--- a/docs/ref/validators.txt
++++ b/docs/ref/validators.txt
+@@ -130,6 +130,11 @@ to, or in lieu of custom ``field.clean()`` methods.
+ :param code: If not ``None``, overrides :attr:`code`.
+ :param allowlist: If not ``None``, overrides :attr:`allowlist`.
+
++ An :class:`EmailValidator` ensures that a value looks like an email, and
++ raises a :exc:`~django.core.exceptions.ValidationError` with
++ :attr:`message` and :attr:`code` if it doesn't. Values longer than 320
++ characters are always considered invalid.
++
+ .. attribute:: message
+
+ The error message used by
+@@ -158,13 +163,19 @@ to, or in lieu of custom ``field.clean()`` methods.
+ The undocumented ``domain_whitelist`` attribute is deprecated. Use
+ ``domain_allowlist`` instead.
+
++ .. versionchanged:: 3.2.20
++
++ In older versions, values longer than 320 characters could be
++ considered valid.
++
+ ``URLValidator``
+ ----------------
+
+ .. class:: URLValidator(schemes=None, regex=None, message=None, code=None)
+
+ A :class:`RegexValidator` subclass that ensures a value looks like a URL,
+- and raises an error code of ``'invalid'`` if it doesn't.
++ and raises an error code of ``'invalid'`` if it doesn't. Values longer than
++ :attr:`max_length` characters are always considered invalid.
+
+ Loopback addresses and reserved IP spaces are considered valid. Literal
+ IPv6 addresses (:rfc:`3986#section-3.2.2`) and Unicode domains are both
+@@ -181,6 +192,18 @@ to, or in lieu of custom ``field.clean()`` methods.
+
+ .. _valid URI schemes: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
+
++ .. attribute:: max_length
++
++ .. versionadded:: 3.2.20
++
++ The maximum length of values that could be considered valid. Defaults
++ to 2048 characters.
++
++ .. versionchanged:: 3.2.20
++
++ In older versions, values longer than 2048 characters could be
++ considered valid.
++
+ ``validate_email``
+ ------------------
+
+diff --git a/tests/forms_tests/field_tests/test_emailfield.py b/tests/forms_tests/field_tests/test_emailfield.py
+index 8b85e4dcc144..19d315205d7e 100644
+--- a/tests/forms_tests/field_tests/test_emailfield.py
++++ b/tests/forms_tests/field_tests/test_emailfield.py
+@@ -9,7 +9,10 @@ class EmailFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
+
+ def test_emailfield_1(self):
+ f = EmailField()
+- self.assertWidgetRendersTo(f, '')
++ self.assertEqual(f.max_length, 320)
++ self.assertWidgetRendersTo(
++ f, ''
++ )
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
+index 26f8ecafea44..82a32af403a0 100644
+--- a/tests/forms_tests/tests/test_forms.py
++++ b/tests/forms_tests/tests/test_forms.py
+@@ -422,11 +422,18 @@ class FormsTestCase(SimpleTestCase):
+ get_spam = BooleanField()
+
+ f = SignupForm(auto_id=False)
+- self.assertHTMLEqual(str(f['email']), '')
++ self.assertHTMLEqual(
++ str(f["email"]),
++ '',
++ )
+ self.assertHTMLEqual(str(f['get_spam']), '')
+
+ f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False)
+- self.assertHTMLEqual(str(f['email']), '')
++ self.assertHTMLEqual(
++ str(f["email"]),
++ '",
++ )
+ self.assertHTMLEqual(
+ str(f['get_spam']),
+ '',
+@@ -2824,7 +2831,7 @@ Good luck picking a username that doesn't already exist.
+
+
+
+-
++
+
+ """
+ )
+@@ -2840,7 +2847,7 @@ Good luck picking a username that doesn't already exist.
+
+
+
+-
++
+
+
+
"""
+@@ -2859,7 +2866,7 @@ Good luck picking a username that doesn't already exist.
+
+
+ |
+- |
++
+ |
+
+ |
"""
+@@ -3489,7 +3496,7 @@ Good luck picking a username that doesn't already exist.
+ f = CommentForm(data, auto_id=False, error_class=DivErrorList)
+ self.assertHTMLEqual(f.as_p(), """Name:
+ Enter a valid email address.
+-Email:
++Email:
+
+ Comment:
""")
+
+diff --git a/tests/validators/tests.py b/tests/validators/tests.py
+index e39d0e3a1cef..1065727a974e 100644
+--- a/tests/validators/tests.py
++++ b/tests/validators/tests.py
+@@ -59,6 +59,7 @@ TEST_DATA = [
+
+ (validate_email, 'example@atm.%s' % ('a' * 64), ValidationError),
+ (validate_email, 'example@%s.atm.%s' % ('b' * 64, 'a' * 63), ValidationError),
++ (validate_email, "example@%scom" % (("a" * 63 + ".") * 100), ValidationError),
+ (validate_email, None, ValidationError),
+ (validate_email, '', ValidationError),
+ (validate_email, 'abc', ValidationError),
+@@ -246,6 +247,16 @@ TEST_DATA = [
+ (URLValidator(), None, ValidationError),
+ (URLValidator(), 56, ValidationError),
+ (URLValidator(), 'no_scheme', ValidationError),
++ (
++ URLValidator(),
++ "http://example." + ("a" * 63 + ".") * 1000 + "com",
++ ValidationError,
++ ),
++ (
++ URLValidator(),
++ "http://userid:password" + "d" * 2000 + "@example.aaaaaaaaaaaaa.com",
++ None,
++ ),
+ # Newlines and tabs are not accepted.
+ (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
+ (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
diff -Nru python-django-3.2.19/debian/patches/0015-CVE-2024-39329.patch python-django-3.2.19/debian/patches/0015-CVE-2024-39329.patch
--- python-django-3.2.19/debian/patches/0015-CVE-2024-39329.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0015-CVE-2024-39329.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,80 @@
+commit 5d8645857936c142a3973694799c52165e2bdcdb
+Author: Michael Manfre
+Date: Fri Jun 14 22:12:58 2024 -0400
+
+ Fixed CVE-2024-39329 -- Standarized timing of verify_password() when checking unusuable passwords.
+
+ Refs #20760.
+
+ Thanks Michael Manfre for the fix and to Adam Johnson for the review.
+
+diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
+index 86ae7f42a..ee81b641d 100644
+--- a/django/contrib/auth/hashers.py
++++ b/django/contrib/auth/hashers.py
+@@ -36,14 +36,20 @@ def check_password(password, encoded, setter=None, preferred='default'):
+ If setter is specified, it'll be called when you need to
+ regenerate the password.
+ """
+- if password is None or not is_password_usable(encoded):
+- return False
++ fake_runtime = password is None or not is_password_usable(encoded)
+
+ preferred = get_hasher(preferred)
+ try:
+ hasher = identify_hasher(encoded)
+ except ValueError:
+ # encoded is gibberish or uses a hasher that's no longer installed.
++ fake_runtime = True
++
++ if fake_runtime:
++ # Run the default password hasher once to reduce the timing difference
++ # between an existing user with an unusable password and a nonexistent
++ # user or missing hasher (similar to #20760).
++ make_password(get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH))
+ return False
+
+ hasher_changed = hasher.algorithm != preferred.algorithm
+diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py
+index 8bc61bc8b..a1ae9400e 100644
+--- a/tests/auth_tests/test_hashers.py
++++ b/tests/auth_tests/test_hashers.py
+@@ -474,6 +474,38 @@ class TestUtilsHashPass(SimpleTestCase):
+ check_password('wrong_password', encoded)
+ self.assertEqual(hasher.harden_runtime.call_count, 1)
+
++ def test_check_password_calls_make_password_to_fake_runtime(self):
++ hasher = get_hasher("default")
++ cases = [
++ (None, None, None), # no plain text password provided
++ ("foo", make_password(password=None), None), # unusable encoded
++ ("letmein", make_password(password="letmein"), ValueError), # valid encoded
++ ]
++ for password, encoded, hasher_side_effect in cases:
++ with (
++ self.subTest(encoded=encoded),
++ mock.patch(
++ "django.contrib.auth.hashers.identify_hasher",
++ side_effect=hasher_side_effect,
++ ) as mock_identify_hasher,
++ mock.patch(
++ "django.contrib.auth.hashers.make_password"
++ ) as mock_make_password,
++ mock.patch(
++ "django.contrib.auth.hashers.get_random_string",
++ side_effect=lambda size: "x" * size,
++ ),
++ mock.patch.object(hasher, "verify"),
++ ):
++ # Ensure make_password is called to standardize timing.
++ check_password(password, encoded)
++ self.assertEqual(hasher.verify.call_count, 0)
++ self.assertEqual(mock_identify_hasher.mock_calls, [mock.call(encoded)])
++ self.assertEqual(
++ mock_make_password.mock_calls,
++ [mock.call("x" * UNUSABLE_PASSWORD_SUFFIX_LENGTH)],
++ )
++
+
+ class BasePasswordHasherTests(SimpleTestCase):
+ not_implemented_msg = 'subclasses of BasePasswordHasher must provide %s() method'
diff -Nru python-django-3.2.19/debian/patches/0016-CVE-2024-39330.patch python-django-3.2.19/debian/patches/0016-CVE-2024-39330.patch
--- python-django-3.2.19/debian/patches/0016-CVE-2024-39330.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0016-CVE-2024-39330.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,145 @@
+commit fe4a0bbe2088d0c2b331216dad21ccd0bb3ee80d
+Author: Natalia <124304+nessita@users.noreply.github.com>
+Date: Wed Mar 20 13:55:21 2024 -0300
+
+ Fixed CVE-2024-39330 -- Added extra file name validation in Storage's save method.
+
+ Thanks to Josh Schneier for the report, and to Carlton Gibson and Sarah
+ Boyce for the reviews.
+
+diff --git a/django/core/files/storage.py b/django/core/files/storage.py
+index 22984f949..680f5ec91 100644
+--- a/django/core/files/storage.py
++++ b/django/core/files/storage.py
+@@ -50,7 +50,18 @@ class Storage:
+ if not hasattr(content, 'chunks'):
+ content = File(content, name)
+
++ # Ensure that the name is valid, before and after having the storage
++ # system potentially modifying the name. This duplicates the check made
++ # inside `get_available_name` but it's necessary for those cases where
++ # `get_available_name` is overriden and validation is lost.
++ validate_file_name(name, allow_relative_path=True)
++
++ # Potentially find a different name depending on storage constraints.
+ name = self.get_available_name(name, max_length=max_length)
++ # Validate the (potentially) new name.
++ validate_file_name(name, allow_relative_path=True)
++
++ # The save operation should return the actual name of the file saved.
+ name = self._save(name, content)
+ # Ensure that the name returned from the storage system is still valid.
+ validate_file_name(name, allow_relative_path=True)
+diff --git a/django/core/files/utils.py b/django/core/files/utils.py
+index 611f932f6e..c730ca17e8 100644
+--- a/django/core/files/utils.py
++++ b/django/core/files/utils.py
+@@ -10,10 +10,9 @@ def validate_file_name(name, allow_relative_path=False):
+ raise SuspiciousFileOperation("Could not derive file name from '%s'" % name)
+
+ if allow_relative_path:
+- # Use PurePosixPath() because this branch is checked only in
+- # FileField.generate_filename() where all file paths are expected to be
+- # Unix style (with forward slashes).
+- path = pathlib.PurePosixPath(name)
++ # Ensure that name can be treated as a pure posix path, i.e. Unix
++ # style (with forward slashes).
++ path = pathlib.PurePosixPath(str(name).replace("\\", "/"))
+ if path.is_absolute() or '..' in path.parts:
+ raise SuspiciousFileOperation(
+ "Detected path traversal attempt in '%s'" % name
+diff --git a/tests/file_storage/test_base.py b/tests/file_storage/test_base.py
+new file mode 100644
+index 0000000000..712d3ba2e2
+--- /dev/null
++++ b/tests/file_storage/test_base.py
+@@ -0,0 +1,72 @@
++import os
++from unittest import mock
++
++from django.core.exceptions import SuspiciousFileOperation
++from django.core.files.storage import Storage
++from django.test import SimpleTestCase
++
++
++class CustomStorage(Storage):
++ """Simple Storage subclass implementing the bare minimum for testing."""
++
++ def exists(self, name):
++ return False
++
++ def _save(self, name):
++ return name
++
++
++class StorageValidateFileNameTests(SimpleTestCase):
++
++ invalid_file_names = [
++ os.path.join("path", "to", os.pardir, "test.file"),
++ os.path.join(os.path.sep, "path", "to", "test.file"),
++ ]
++ error_msg = "Detected path traversal attempt in '%s'"
++
++ def test_validate_before_get_available_name(self):
++ s = CustomStorage()
++ # The initial name passed to `save` is not valid nor safe, fail early.
++ for name in self.invalid_file_names:
++ with (
++ self.subTest(name=name),
++ mock.patch.object(s, "get_available_name") as mock_get_available_name,
++ mock.patch.object(s, "_save") as mock_internal_save,
++ ):
++ with self.assertRaisesMessage(
++ SuspiciousFileOperation, self.error_msg % name
++ ):
++ s.save(name, content="irrelevant")
++ self.assertEqual(mock_get_available_name.mock_calls, [])
++ self.assertEqual(mock_internal_save.mock_calls, [])
++
++ def test_validate_after_get_available_name(self):
++ s = CustomStorage()
++ # The initial name passed to `save` is valid and safe, but the returned
++ # name from `get_available_name` is not.
++ for name in self.invalid_file_names:
++ with (
++ self.subTest(name=name),
++ mock.patch.object(s, "get_available_name", return_value=name),
++ mock.patch.object(s, "_save") as mock_internal_save,
++ ):
++ with self.assertRaisesMessage(
++ SuspiciousFileOperation, self.error_msg % name
++ ):
++ s.save("valid-file-name.txt", content="irrelevant")
++ self.assertEqual(mock_internal_save.mock_calls, [])
++
++ def test_validate_after_internal_save(self):
++ s = CustomStorage()
++ # The initial name passed to `save` is valid and safe, but the result
++ # from `_save` is not (this is achieved by monkeypatching _save).
++ for name in self.invalid_file_names:
++ with (
++ self.subTest(name=name),
++ mock.patch.object(s, "_save", return_value=name),
++ ):
++
++ with self.assertRaisesMessage(
++ SuspiciousFileOperation, self.error_msg % name
++ ):
++ s.save("valid-file-name.txt", content="irrelevant")
+diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
+index 723809324..6d17a7118 100644
+--- a/tests/file_storage/tests.py
++++ b/tests/file_storage/tests.py
+@@ -297,12 +297,6 @@ class FileStorageTests(SimpleTestCase):
+
+ self.storage.delete('path/to/test.file')
+
+- def test_file_save_abs_path(self):
+- test_name = 'path/to/test.file'
+- f = ContentFile('file saved with path')
+- f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
+- self.assertEqual(f_name, test_name)
+-
+ def test_save_doesnt_close(self):
+ with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
+ file.write(b'1')
diff -Nru python-django-3.2.19/debian/patches/0017-CVE-2024-39614-1.patch python-django-3.2.19/debian/patches/0017-CVE-2024-39614-1.patch
--- python-django-3.2.19/debian/patches/0017-CVE-2024-39614-1.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0017-CVE-2024-39614-1.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,127 @@
+commit 9e9792228a6bb5d6402a5d645bc3be4cf364aefb
+Author: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
+Date: Wed Jun 26 12:11:54 2024 +0200
+
+ Fixed CVE-2024-39614 -- Mitigated potential DoS in get_supported_language_variant().
+
+ Language codes are now parsed with a maximum length limit of 500 chars.
+
+ Thanks to MProgrammer for the report.
+
+diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
+index b262a5000..92442185f 100644
+--- a/django/utils/translation/trans_real.py
++++ b/django/utils/translation/trans_real.py
+@@ -31,9 +31,10 @@ _default = None
+ CONTEXT_SEPARATOR = "\x04"
+
+ # Maximum number of characters that will be parsed from the Accept-Language
+-# header to prevent possible denial of service or memory exhaustion attacks.
+-# About 10x longer than the longest value shown on MDN’s Accept-Language page.
+-ACCEPT_LANGUAGE_HEADER_MAX_LENGTH = 500
++# header or cookie to prevent possible denial of service or memory exhaustion
++# attacks. About 10x longer than the longest value shown on MDN’s
++# Accept-Language page.
++LANGUAGE_CODE_MAX_LENGTH = 500
+
+ # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9
+ # and RFC 3066, section 2.1
+@@ -474,11 +475,25 @@ def get_supported_language_variant(lang_code, strict=False):
+ If `strict` is False (the default), look for a country-specific variant
+ when neither the language code nor its generic variant is found.
+
++ The language code is truncated to a maximum length to avoid potential
++ denial of service attacks.
++
+ lru_cache should have a maxsize to prevent from memory exhaustion attacks,
+ as the provided language codes are taken from the HTTP request. See also
+ .
+ """
+ if lang_code:
++ # Truncate the language code to a maximum length to avoid potential
++ # denial of service attacks.
++ if len(lang_code) > LANGUAGE_CODE_MAX_LENGTH:
++ if (
++ not strict
++ and (index := lang_code.rfind("-", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0
++ ):
++ # There is a generic variant under the maximum length accepted length.
++ lang_code = lang_code[:index]
++ else:
++ raise ValueError("'lang_code' exceeds the maximum accepted length")
+ # If 'fr-ca' is not supported, try special fallback or language-only 'fr'.
+ possible_lang_codes = [lang_code]
+ try:
+@@ -595,13 +610,13 @@ def parse_accept_lang_header(lang_string):
+ functools.lru_cache() to avoid repetitive parsing of common header values.
+ """
+ # If the header value doesn't exceed the maximum allowed length, parse it.
+- if len(lang_string) <= ACCEPT_LANGUAGE_HEADER_MAX_LENGTH:
++ if len(lang_string) <= LANGUAGE_CODE_MAX_LENGTH:
+ return _parse_accept_lang_header(lang_string)
+
+ # If there is at least one comma in the value, parse up to the last comma
+ # before the max length, skipping any truncated parts at the end of the
+ # header value.
+- index = lang_string.rfind(",", 0, ACCEPT_LANGUAGE_HEADER_MAX_LENGTH)
++ index = lang_string.rfind(",", 0, LANGUAGE_CODE_MAX_LENGTH)
+ if index > 0:
+ return _parse_accept_lang_header(lang_string[:index])
+
+diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
+index ce3a4cba0..00a3f5e79 100644
+--- a/docs/ref/utils.txt
++++ b/docs/ref/utils.txt
+@@ -1129,6 +1129,11 @@ For a complete discussion on the usage of the following see the
+ ``lang_code`` is ``'es-ar'`` and ``'es'`` is in :setting:`LANGUAGES` but
+ ``'es-ar'`` isn't.
+
++ ``lang_code`` has a maximum accepted length of 500 characters. A
++ :exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
++ ``strict`` is ``True``, or if there is no generic variant and ``strict``
++ is ``False``.
++
+ If ``strict`` is ``False`` (the default), a country-specific variant may
+ be returned when neither the language code nor its generic variant is found.
+ For example, if only ``'es-co'`` is in :setting:`LANGUAGES`, that's
+@@ -1137,6 +1142,11 @@ For a complete discussion on the usage of the following see the
+
+ Raises :exc:`LookupError` if nothing is found.
+
++ .. versionchanged:: 4.2.14
++
++ In older versions, ``lang_code`` values over 500 characters were
++ processed without raising a :exc:`ValueError`.
++
+ .. function:: to_locale(language)
+
+ Turns a language name (en-us) into a locale name (en_US).
+diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
+index 41ec63da9..793bd3db8 100644
+--- a/tests/i18n/tests.py
++++ b/tests/i18n/tests.py
+@@ -40,6 +40,7 @@ from django.utils.translation import (
+ from django.utils.translation.reloader import (
+ translation_file_changed, watch_for_translation_changes,
+ )
++from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
+
+ from .forms import CompanyForm, I18nForm, SelectDateForm
+ from .models import Company, TestModel
+@@ -1532,6 +1533,16 @@ class MiscTests(SimpleTestCase):
+ g('xyz')
+ with self.assertRaises(LookupError):
+ g('xy-zz')
++ msg = "'lang_code' exceeds the maximum accepted length"
++ with self.assertRaises(LookupError):
++ g("x" * LANGUAGE_CODE_MAX_LENGTH)
++ with self.assertRaisesMessage(ValueError, msg):
++ g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
++ # 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
++ self.assertEqual(g("en-" * 167), "en")
++ with self.assertRaisesMessage(ValueError, msg):
++ g("en-" * 167, strict=True)
++ self.assertEqual(g("en-" * 30000), "en") # catastrophic test
+
+ def test_get_supported_language_variant_null(self):
+ g = trans_null.get_supported_language_variant
diff -Nru python-django-3.2.19/debian/patches/0018-CVE-2024-39614-2.patch python-django-3.2.19/debian/patches/0018-CVE-2024-39614-2.patch
--- python-django-3.2.19/debian/patches/0018-CVE-2024-39614-2.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0018-CVE-2024-39614-2.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,92 @@
+commit 0e94f292cda632153f2b3d9a9037eb0141ae9c2e
+Author: Lorenzo Peña
+Date: Tue Jul 23 12:06:29 2024 +0200
+
+ Fixed #35627 -- Raised a LookupError rather than an unhandled ValueError in get_supported_language_variant().
+
+ LocaleMiddleware didn't handle the ValueError raised by
+ get_supported_language_variant() when language codes were
+ over 500 characters.
+
+ Regression in 9e9792228a6bb5d6402a5d645bc3be4cf364aefb.
+
+Index: python-django-debian.git/django/utils/translation/trans_real.py
+===================================================================
+--- python-django-debian.git.orig/django/utils/translation/trans_real.py
++++ python-django-debian.git/django/utils/translation/trans_real.py
+@@ -493,7 +493,7 @@ def get_supported_language_variant(lang_
+ # There is a generic variant under the maximum length accepted length.
+ lang_code = lang_code[:index]
+ else:
+- raise ValueError("'lang_code' exceeds the maximum accepted length")
++ raise LookupError(lang_code)
+ # If 'fr-ca' is not supported, try special fallback or language-only 'fr'.
+ possible_lang_codes = [lang_code]
+ try:
+Index: python-django-debian.git/docs/ref/utils.txt
+===================================================================
+--- python-django-debian.git.orig/docs/ref/utils.txt
++++ python-django-debian.git/docs/ref/utils.txt
+@@ -1130,7 +1130,7 @@ For a complete discussion on the usage o
+ ``'es-ar'`` isn't.
+
+ ``lang_code`` has a maximum accepted length of 500 characters. A
+- :exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
++ :exc:`LookupError` is raised if ``lang_code`` exceeds this limit and
+ ``strict`` is ``True``, or if there is no generic variant and ``strict``
+ is ``False``.
+
+@@ -1142,10 +1142,10 @@ For a complete discussion on the usage o
+
+ Raises :exc:`LookupError` if nothing is found.
+
+- .. versionchanged:: 4.2.14
++ .. versionchanged:: 4.2.15
+
+ In older versions, ``lang_code`` values over 500 characters were
+- processed without raising a :exc:`ValueError`.
++ processed without raising a :exc:`LookupError`.
+
+ .. function:: to_locale(language)
+
+Index: python-django-debian.git/tests/i18n/tests.py
+===================================================================
+--- python-django-debian.git.orig/tests/i18n/tests.py
++++ python-django-debian.git/tests/i18n/tests.py
+@@ -1533,14 +1533,13 @@ class MiscTests(SimpleTestCase):
+ g('xyz')
+ with self.assertRaises(LookupError):
+ g('xy-zz')
+- msg = "'lang_code' exceeds the maximum accepted length"
+ with self.assertRaises(LookupError):
+ g("x" * LANGUAGE_CODE_MAX_LENGTH)
+- with self.assertRaisesMessage(ValueError, msg):
++ with self.assertRaises(LookupError):
+ g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
+ # 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
+ self.assertEqual(g("en-" * 167), "en")
+- with self.assertRaisesMessage(ValueError, msg):
++ with self.assertRaises(LookupError):
+ g("en-" * 167, strict=True)
+ self.assertEqual(g("en-" * 30000), "en") # catastrophic test
+
+@@ -1579,6 +1578,7 @@ class MiscTests(SimpleTestCase):
+ self.assertEqual(g('/de-at/'), 'de-at')
+ self.assertEqual(g('/de-ch/'), 'de')
+ self.assertIsNone(g('/de-simple-page/'))
++ self.assertIsNone(g(f"/{'a' * 501}/"))
+
+ def test_get_language_from_path_null(self):
+ g = trans_null.get_language_from_path
+@@ -1866,6 +1866,11 @@ class CountrySpecificLanguageTests(Simpl
+ lang = get_language_from_request(r)
+ self.assertEqual('bg', lang)
+
++ def test_get_language_from_request_code_too_long(self):
++ request = self.rf.get("/", headers={"accept-language": "a" * 501})
++ lang = get_language_from_request(request)
++ self.assertEqual("en-us", lang)
++
+ def test_get_language_from_request_null(self):
+ lang = trans_null.get_language_from_request(None)
+ self.assertEqual(lang, 'en')
diff -Nru python-django-3.2.19/debian/patches/0019-CVE-2024-41989.patch python-django-3.2.19/debian/patches/0019-CVE-2024-41989.patch
--- python-django-3.2.19/debian/patches/0019-CVE-2024-41989.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0019-CVE-2024-41989.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,67 @@
+commit c19465ad87e33b6122c886b97a202ad54cd43672
+Author: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
+Date: Fri Jul 12 11:38:34 2024 +0200
+
+ Fixed CVE-2024-41989 -- Prevented excessive memory consumption in floatformat.
+
+ Thanks Elias Myllymäki for the report.
+
+ Co-authored-by: Shai Berger
+
+diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
+index 02cac06bcf..66c6e76d20 100644
+--- a/django/template/defaultfilters.py
++++ b/django/template/defaultfilters.py
+@@ -146,6 +146,19 @@ def floatformat(text, arg=-1):
+ except ValueError:
+ return input_val
+
++ _, digits, exponent = d.as_tuple()
++ try:
++ number_of_digits_and_exponent_sum = len(digits) + abs(exponent)
++ except TypeError:
++ # Exponent values can be "F", "n", "N".
++ number_of_digits_and_exponent_sum = 0
++
++ # Values with more than 200 digits, or with a large exponent, are returned "as is"
++ # to avoid high memory consumption and potential denial-of-service attacks.
++ # The cut-off of 200 is consistent with django.utils.numberformat.floatformat().
++ if number_of_digits_and_exponent_sum > 200:
++ return input_val
++
+ try:
+ m = int(d) - d
+ except (ValueError, OverflowError, InvalidOperation):
+diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py
+index 145858b75f..3d6c34a552 100644
+--- a/tests/template_tests/filter_tests/test_floatformat.py
++++ b/tests/template_tests/filter_tests/test_floatformat.py
+@@ -59,6 +59,7 @@ class FunctionTests(SimpleTestCase):
+ self.assertEqual(floatformat(1.5e-15, 20), '0.00000000000000150000')
+ self.assertEqual(floatformat(1.5e-15, -20), '0.00000000000000150000')
+ self.assertEqual(floatformat(1.00000000000000015, 16), '1.0000000000000002')
++ self.assertEqual(floatformat("1e199"), "1" + "0" * 199)
+
+ @override_settings(USE_L10N=True)
+ def test_force_grouping(self):
+@@ -96,6 +97,20 @@ class FunctionTests(SimpleTestCase):
+ self.assertEqual(floatformat(pos_inf), 'inf')
+ self.assertEqual(floatformat(neg_inf), '-inf')
+ self.assertEqual(floatformat(pos_inf / pos_inf), 'nan')
++
++ def test_too_many_digits_to_render(self):
++ cases = [
++ "1e200",
++ "1E200",
++ "1E10000000000000000",
++ "-1E10000000000000000",
++ "1e10000000000000000",
++ "-1e10000000000000000",
++ "1" + "0" * 1_000_000,
++ ]
++ for value in cases:
++ with self.subTest(value=value):
++ self.assertEqual(floatformat(value), "'" + value + "'")
+
+ def test_float_dunder_method(self):
+ class FloatWrapper:
diff -Nru python-django-3.2.19/debian/patches/0020-CVE-2024-41991.patch python-django-3.2.19/debian/patches/0020-CVE-2024-41991.patch
--- python-django-3.2.19/debian/patches/0020-CVE-2024-41991.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0020-CVE-2024-41991.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,108 @@
+commit 5f1757142febd95994caa1c0f64c1a0c161982c3
+Author: Mariusz Felisiak
+Date: Wed Jul 10 20:30:12 2024 +0200
+
+ Fixed CVE-2024-41991 -- Prevented potential ReDoS in django.utils.html.urlize() and AdminURLFieldWidget.
+
+ Thanks Seokchan Yoon for the report.
+
+ Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
+
+diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
+index aeb74773a..b7dd0d87a 100644
+--- a/django/contrib/admin/widgets.py
++++ b/django/contrib/admin/widgets.py
+@@ -339,7 +339,7 @@ class AdminURLFieldWidget(forms.URLInput):
+ context = super().get_context(name, value, attrs)
+ context['current_label'] = _('Currently:')
+ context['change_label'] = _('Change:')
+- context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
++ context['widget']['href'] = smart_urlquote(context['widget']['value']) if url_valid else ''
+ context['url_valid'] = url_valid
+ return context
+
+diff --git a/django/utils/html.py b/django/utils/html.py
+index 1123e38f6c..154c820d34 100644
+--- a/django/utils/html.py
++++ b/django/utils/html.py
+@@ -28,7 +28,7 @@ simple_url_2_re = _lazy_re_compile(
+ r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$',
+ re.IGNORECASE
+ )
+-
++MAX_URL_LENGTH = 2048
+
+ @keep_lazy(str, SafeString)
+ def escape(text):
+@@ -298,6 +298,10 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
+ except ValueError:
+ # value contains more than one @.
+ return False
++ # Max length for domain name labels is 63 characters per RFC 1034.
++ # Helps to avoid ReDoS vectors in the domain part.
++ if len(p2) > 63:
++ return False
+ # Dot must be in p2 (e.g. example.com)
+ if '.' not in p2 or p2.startswith('.'):
+ return False
+@@ -316,9 +322,9 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
+ # Make URL we want to point to.
+ url = None
+ nofollow_attr = ' rel="nofollow"' if nofollow else ''
+- if simple_url_re.match(middle):
++ if len(middle) <= MAX_URL_LENGTH and simple_url_re.match(middle):
+ url = smart_urlquote(html.unescape(middle))
+- elif simple_url_2_re.match(middle):
++ elif len(middle) <= MAX_URL_LENGTH and simple_url_2_re.match(middle):
+ url = smart_urlquote('http://%s' % html.unescape(middle))
+ elif ':' not in middle and is_email_simple(middle):
+ local, domain = middle.rsplit('@', 1)
+diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
+index 517e060b80..5da4adf8c9 100644
+--- a/tests/admin_widgets/tests.py
++++ b/tests/admin_widgets/tests.py
+@@ -353,7 +353,12 @@ class AdminSplitDateTimeWidgetTest(SimpleTestCase):
+ class AdminURLWidgetTest(SimpleTestCase):
+ def test_get_context_validates_url(self):
+ w = widgets.AdminURLFieldWidget()
+- for invalid in ['', '/not/a/full/url/', 'javascript:alert("Danger XSS!")']:
++ for invalid in [
++ "",
++ "/not/a/full/url/",
++ 'javascript:alert("Danger XSS!")',
++ "http://" + "한.글." * 1_000_000 + "com",
++ ]:
+ with self.subTest(url=invalid):
+ self.assertFalse(w.get_context('name', invalid, {})['url_valid'])
+ self.assertTrue(w.get_context('name', 'http://example.com', {})['url_valid'])
+diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
+index 30f5ba68e..93458acd1 100644
+--- a/tests/utils_tests/test_html.py
++++ b/tests/utils_tests/test_html.py
+@@ -255,6 +255,15 @@ class TestUtilsHtml(SimpleTestCase):
+ 'Search for google.com/?q=!'
+ ),
+ ('foo@example.com', 'foo@example.com'),
++ (
++ "test@" + "한.글." * 15 + "aaa",
++ ''
++ + "test@"
++ + "한.글." * 15
++ + "aaa",
++ ),
+ )
+ for value, output in tests:
+ with self.subTest(value=value):
+@@ -263,6 +272,10 @@ class TestUtilsHtml(SimpleTestCase):
+ def test_urlize_unchanged_inputs(self):
+ tests = (
+ ('a' + '@a' * 50000) + 'a', # simple_email_re catastrophic test
++ # Unicode domain catastrophic tests.
++ "a@" + "한.글." * 1_000_000 + "a",
++ "http://" + "한.글." * 1_000_000 + "com",
++ "www." + "한.글." * 1_000_000 + "com",
+ ('a' + '.' * 1000000) + 'a', # trailing_punctuation catastrophic test
+ 'foo@',
+ '@foo.com',
diff -Nru python-django-3.2.19/debian/patches/0021-CVE-2024-42005.patch python-django-3.2.19/debian/patches/0021-CVE-2024-42005.patch
--- python-django-3.2.19/debian/patches/0021-CVE-2024-42005.patch 1970-01-01 00:00:00.000000000 +0000
+++ python-django-3.2.19/debian/patches/0021-CVE-2024-42005.patch 2024-08-21 11:08:24.000000000 +0000
@@ -0,0 +1,70 @@
+commit c87bfaacf8fb84984243b5055dc70f97996cb115
+Author: Simon Charette
+Date: Thu Jul 25 12:19:13 2024 -0400
+
+ Fixed CVE-2024-42005 -- Mitigated QuerySet.values() SQL injection attacks against JSON fields.
+
+ Thanks Eyal (eyalgabay) for the report.
+
+Index: python-django-debian.git/django/db/models/sql/query.py
+===================================================================
+--- python-django-debian.git.orig/django/db/models/sql/query.py
++++ python-django-debian.git/django/db/models/sql/query.py
+@@ -2239,6 +2239,8 @@ class Query(BaseExpression):
+ self.clear_select_fields()
+
+ if fields:
++ for field in fields:
++ self.check_alias(field)
+ field_names = []
+ extra_names = []
+ annotation_names = []
+Index: python-django-debian.git/tests/expressions/models.py
+===================================================================
+--- python-django-debian.git.orig/tests/expressions/models.py
++++ python-django-debian.git/tests/expressions/models.py
+@@ -101,3 +101,10 @@ class UUIDPK(models.Model):
+ class UUID(models.Model):
+ uuid = models.UUIDField(null=True)
+ uuid_fk = models.ForeignKey(UUIDPK, models.CASCADE, null=True)
++
++
++class JSONFieldModel(models.Model):
++ data = models.JSONField(null=True)
++
++ class Meta:
++ required_db_features = {"supports_json_field"}
+Index: python-django-debian.git/tests/expressions/test_queryset_values.py
+===================================================================
+--- python-django-debian.git.orig/tests/expressions/test_queryset_values.py
++++ python-django-debian.git/tests/expressions/test_queryset_values.py
+@@ -1,7 +1,7 @@
+ from django.db.models import F, Sum
+-from django.test import TestCase
++from django.test import TestCase, skipUnlessDBFeature
+
+-from .models import Company, Employee
++from .models import Company, Employee, JSONFieldModel
+
+
+ class ValuesExpressionsTests(TestCase):
+@@ -35,6 +35,19 @@ class ValuesExpressionsTests(TestCase):
+ with self.assertRaisesMessage(ValueError, msg):
+ Company.objects.values(**{crafted_alias: F("ceo__salary")})
+
++ @skipUnlessDBFeature("supports_json_field")
++ def test_values_expression_alias_sql_injection_json_field(self):
++ crafted_alias = """injected_name" from "expressions_company"; --"""
++ msg = (
++ "Column aliases cannot contain whitespace characters, quotation marks, "
++ "semicolons, or SQL comments."
++ )
++ with self.assertRaisesMessage(ValueError, msg):
++ JSONFieldModel.objects.values(f"data__{crafted_alias}")
++
++ with self.assertRaisesMessage(ValueError, msg):
++ JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
++
+ def test_values_expression_group_by(self):
+ # values() applies annotate() first, so values selected are grouped by
+ # id, not firstname.
diff -Nru python-django-3.2.19/debian/patches/CVE-2023-36053.patch python-django-3.2.19/debian/patches/CVE-2023-36053.patch
--- python-django-3.2.19/debian/patches/CVE-2023-36053.patch 2023-07-28 13:24:04.000000000 +0000
+++ python-django-3.2.19/debian/patches/CVE-2023-36053.patch 1970-01-01 00:00:00.000000000 +0000
@@ -1,242 +0,0 @@
-From: Mariusz Felisiak
-Date: Wed, 14 Jun 2023 12:23:06 +0200
-Subject: [PATCH] [3.2.x] Fixed CVE-2023-36053 -- Prevented potential ReDoS in
- EmailValidator and URLValidator.
-
-Thanks Seokchan Yoon for reports.
----
- django/core/validators.py | 7 +++++--
- django/forms/fields.py | 3 +++
- docs/ref/forms/fields.txt | 7 ++++++-
- docs/ref/validators.txt | 25 +++++++++++++++++++++++-
- tests/forms_tests/field_tests/test_emailfield.py | 5 ++++-
- tests/forms_tests/tests/test_forms.py | 19 ++++++++++++------
- tests/validators/tests.py | 11 +++++++++++
- 7 files changed, 66 insertions(+), 11 deletions(-)
-
-diff --git a/django/core/validators.py b/django/core/validators.py
-index 6b28eef08dd2..52ebddac6345 100644
---- a/django/core/validators.py
-+++ b/django/core/validators.py
-@@ -93,6 +93,7 @@ class URLValidator(RegexValidator):
- message = _('Enter a valid URL.')
- schemes = ['http', 'https', 'ftp', 'ftps']
- unsafe_chars = frozenset('\t\r\n')
-+ max_length = 2048
-
- def __init__(self, schemes=None, **kwargs):
- super().__init__(**kwargs)
-@@ -100,7 +101,7 @@ class URLValidator(RegexValidator):
- self.schemes = schemes
-
- def __call__(self, value):
-- if not isinstance(value, str):
-+ if not isinstance(value, str) or len(value) > self.max_length:
- raise ValidationError(self.message, code=self.code, params={'value': value})
- if self.unsafe_chars.intersection(value):
- raise ValidationError(self.message, code=self.code, params={'value': value})
-@@ -211,7 +212,9 @@ class EmailValidator:
- self.domain_allowlist = allowlist
-
- def __call__(self, value):
-- if not value or '@' not in value:
-+ # The maximum length of an email is 320 characters per RFC 3696
-+ # section 3.
-+ if not value or '@' not in value or len(value) > 320:
- raise ValidationError(self.message, code=self.code, params={'value': value})
-
- user_part, domain_part = value.rsplit('@', 1)
-diff --git a/django/forms/fields.py b/django/forms/fields.py
-index 0214d60c1cf1..8adb09e38294 100644
---- a/django/forms/fields.py
-+++ b/django/forms/fields.py
-@@ -540,6 +540,9 @@ class EmailField(CharField):
- default_validators = [validators.validate_email]
-
- def __init__(self, **kwargs):
-+ # The default maximum length of an email is 320 characters per RFC 3696
-+ # section 3.
-+ kwargs.setdefault("max_length", 320)
- super().__init__(strip=True, **kwargs)
-
-
-diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
-index 9438214a28ce..5b485f215384 100644
---- a/docs/ref/forms/fields.txt
-+++ b/docs/ref/forms/fields.txt
-@@ -592,7 +592,12 @@ For each field, we describe the default widget used if you don't specify
- * Error message keys: ``required``, ``invalid``
-
- Has three optional arguments ``max_length``, ``min_length``, and
-- ``empty_value`` which work just as they do for :class:`CharField`.
-+ ``empty_value`` which work just as they do for :class:`CharField`. The
-+ ``max_length`` argument defaults to 320 (see :rfc:`3696#section-3`).
-+
-+ .. versionchanged:: 3.2.20
-+
-+ The default value for ``max_length`` was changed to 320 characters.
-
- ``FileField``
- -------------
-diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt
-index 50761e5a425c..b22762b17b93 100644
---- a/docs/ref/validators.txt
-+++ b/docs/ref/validators.txt
-@@ -130,6 +130,11 @@ to, or in lieu of custom ``field.clean()`` methods.
- :param code: If not ``None``, overrides :attr:`code`.
- :param allowlist: If not ``None``, overrides :attr:`allowlist`.
-
-+ An :class:`EmailValidator` ensures that a value looks like an email, and
-+ raises a :exc:`~django.core.exceptions.ValidationError` with
-+ :attr:`message` and :attr:`code` if it doesn't. Values longer than 320
-+ characters are always considered invalid.
-+
- .. attribute:: message
-
- The error message used by
-@@ -158,13 +163,19 @@ to, or in lieu of custom ``field.clean()`` methods.
- The undocumented ``domain_whitelist`` attribute is deprecated. Use
- ``domain_allowlist`` instead.
-
-+ .. versionchanged:: 3.2.20
-+
-+ In older versions, values longer than 320 characters could be
-+ considered valid.
-+
- ``URLValidator``
- ----------------
-
- .. class:: URLValidator(schemes=None, regex=None, message=None, code=None)
-
- A :class:`RegexValidator` subclass that ensures a value looks like a URL,
-- and raises an error code of ``'invalid'`` if it doesn't.
-+ and raises an error code of ``'invalid'`` if it doesn't. Values longer than
-+ :attr:`max_length` characters are always considered invalid.
-
- Loopback addresses and reserved IP spaces are considered valid. Literal
- IPv6 addresses (:rfc:`3986#section-3.2.2`) and Unicode domains are both
-@@ -181,6 +192,18 @@ to, or in lieu of custom ``field.clean()`` methods.
-
- .. _valid URI schemes: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
-
-+ .. attribute:: max_length
-+
-+ .. versionadded:: 3.2.20
-+
-+ The maximum length of values that could be considered valid. Defaults
-+ to 2048 characters.
-+
-+ .. versionchanged:: 3.2.20
-+
-+ In older versions, values longer than 2048 characters could be
-+ considered valid.
-+
- ``validate_email``
- ------------------
-
-diff --git a/tests/forms_tests/field_tests/test_emailfield.py b/tests/forms_tests/field_tests/test_emailfield.py
-index 8b85e4dcc144..19d315205d7e 100644
---- a/tests/forms_tests/field_tests/test_emailfield.py
-+++ b/tests/forms_tests/field_tests/test_emailfield.py
-@@ -9,7 +9,10 @@ class EmailFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
-
- def test_emailfield_1(self):
- f = EmailField()
-- self.assertWidgetRendersTo(f, '')
-+ self.assertEqual(f.max_length, 320)
-+ self.assertWidgetRendersTo(
-+ f, ''
-+ )
- with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
- f.clean('')
- with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
-diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
-index 26f8ecafea44..82a32af403a0 100644
---- a/tests/forms_tests/tests/test_forms.py
-+++ b/tests/forms_tests/tests/test_forms.py
-@@ -422,11 +422,18 @@ class FormsTestCase(SimpleTestCase):
- get_spam = BooleanField()
-
- f = SignupForm(auto_id=False)
-- self.assertHTMLEqual(str(f['email']), '')
-+ self.assertHTMLEqual(
-+ str(f["email"]),
-+ '',
-+ )
- self.assertHTMLEqual(str(f['get_spam']), '')
-
- f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False)
-- self.assertHTMLEqual(str(f['email']), '')
-+ self.assertHTMLEqual(
-+ str(f["email"]),
-+ '",
-+ )
- self.assertHTMLEqual(
- str(f['get_spam']),
- '',
-@@ -2824,7 +2831,7 @@ Good luck picking a username that doesn't already exist.
-
-
-
--
-+
-
- """
- )
-@@ -2840,7 +2847,7 @@ Good luck picking a username that doesn't already exist.
-
-
-
--
-+
-
-
-
"""
-@@ -2859,7 +2866,7 @@ Good luck picking a username that doesn't already exist.
-
-
- |
-- |
-+
- |
-
- |
"""
-@@ -3489,7 +3496,7 @@ Good luck picking a username that doesn't already exist.
- f = CommentForm(data, auto_id=False, error_class=DivErrorList)
- self.assertHTMLEqual(f.as_p(), """Name:
- Enter a valid email address.
--Email:
-+Email:
-
- Comment:
""")
-
-diff --git a/tests/validators/tests.py b/tests/validators/tests.py
-index e39d0e3a1cef..1065727a974e 100644
---- a/tests/validators/tests.py
-+++ b/tests/validators/tests.py
-@@ -59,6 +59,7 @@ TEST_DATA = [
-
- (validate_email, 'example@atm.%s' % ('a' * 64), ValidationError),
- (validate_email, 'example@%s.atm.%s' % ('b' * 64, 'a' * 63), ValidationError),
-+ (validate_email, "example@%scom" % (("a" * 63 + ".") * 100), ValidationError),
- (validate_email, None, ValidationError),
- (validate_email, '', ValidationError),
- (validate_email, 'abc', ValidationError),
-@@ -246,6 +247,16 @@ TEST_DATA = [
- (URLValidator(), None, ValidationError),
- (URLValidator(), 56, ValidationError),
- (URLValidator(), 'no_scheme', ValidationError),
-+ (
-+ URLValidator(),
-+ "http://example." + ("a" * 63 + ".") * 1000 + "com",
-+ ValidationError,
-+ ),
-+ (
-+ URLValidator(),
-+ "http://userid:password" + "d" * 2000 + "@example.aaaaaaaaaaaaa.com",
-+ None,
-+ ),
- # Newlines and tabs are not accepted.
- (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
- (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
diff -Nru python-django-3.2.19/debian/patches/series python-django-3.2.19/debian/patches/series
--- python-django-3.2.19/debian/patches/series 2023-07-28 13:24:04.000000000 +0000
+++ python-django-3.2.19/debian/patches/series 2024-08-21 11:08:24.000000000 +0000
@@ -10,4 +10,11 @@
0011-Moved-RequestSite-import-to-the-toplevel.patch
0012-Add-Python-3.11-support-for-tests.patch
0013-fix-url-validator.patch
-CVE-2023-36053.patch
+0014-CVE-2023-36053.patch
+0015-CVE-2024-39329.patch
+0016-CVE-2024-39330.patch
+0017-CVE-2024-39614-1.patch
+0018-CVE-2024-39614-2.patch
+0019-CVE-2024-41989.patch
+0020-CVE-2024-41991.patch
+0021-CVE-2024-42005.patch