diff --git a/CVE-2022-34265.patch b/CVE-2022-34265.patch
deleted file mode 100644
index 3d4f452e08f999e126286f2217083e5c68fc71fa..0000000000000000000000000000000000000000
--- a/CVE-2022-34265.patch
+++ /dev/null
@@ -1,109 +0,0 @@
-From a9010fe5555e6086a9d9ae50069579400ef0685e Mon Sep 17 00:00:00 2001
-From: Mariusz Felisiak
-Date: Wed, 22 Jun 2022 12:44:04 +0200
-Subject: [PATCH] [3.2.x] Fixed CVE-2022-34265 -- Protected
- Trunc(kind)/Extract(lookup_name) against SQL injection.
-
-Thanks Takuto Yoshikai (Aeye Security Lab) for the report.
----
- django/db/backends/base/operations.py | 3 ++
- django/db/models/functions/datetime.py | 4 +++
- docs/releases/3.2.14.txt | 11 ++++++
- .../datetime/test_extract_trunc.py | 34 +++++++++++++++++++
- 4 files changed, 52 insertions(+)
-
-diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
-index 0fcc607bcfb0..cdcd9885ba27 100644
---- a/django/db/backends/base/operations.py
-+++ b/django/db/backends/base/operations.py
-@@ -9,6 +9,7 @@
- from django.db.backends import utils
- from django.utils import timezone
- from django.utils.encoding import force_str
-+from django.utils.regex_helper import _lazy_re_compile
-
-
- class BaseDatabaseOperations:
-@@ -53,6 +54,8 @@ class BaseDatabaseOperations:
- # Prefix for EXPLAIN queries, or None EXPLAIN isn't supported.
- explain_prefix = None
-
-+ extract_trunc_lookup_pattern = _lazy_re_compile(r"[\w\-_()]+")
-+
- def __init__(self, connection):
- self.connection = connection
- self._cache = None
-diff --git a/django/db/models/functions/datetime.py b/django/db/models/functions/datetime.py
-index 90e6f41be057..47651d281f19 100644
---- a/django/db/models/functions/datetime.py
-+++ b/django/db/models/functions/datetime.py
-@@ -41,6 +41,8 @@ def __init__(self, expression, lookup_name=None, tzinfo=None, **extra):
- super().__init__(expression, **extra)
-
- def as_sql(self, compiler, connection):
-+ if not connection.ops.extract_trunc_lookup_pattern.fullmatch(self.lookup_name):
-+ raise ValueError("Invalid lookup_name: %s" % self.lookup_name)
- sql, params = compiler.compile(self.lhs)
- lhs_output_field = self.lhs.output_field
- if isinstance(lhs_output_field, DateTimeField):
-@@ -192,6 +194,8 @@ def __init__(self, expression, output_field=None, tzinfo=None, is_dst=None, **ex
- super().__init__(expression, output_field=output_field, **extra)
-
- def as_sql(self, compiler, connection):
-+ if not connection.ops.extract_trunc_lookup_pattern.fullmatch(self.kind):
-+ raise ValueError("Invalid kind: %s" % self.kind)
- inner_sql, inner_params = compiler.compile(self.lhs)
- tzname = None
- if isinstance(self.lhs.output_field, DateTimeField):
-diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py
-index 258600127f93..27ed3ae63ee5 100644
---- a/tests/db_functions/datetime/test_extract_trunc.py
-+++ b/tests/db_functions/datetime/test_extract_trunc.py
-@@ -177,6 +177,23 @@ def test_extract_year_lessthan_lookup(self):
- self.assertEqual(qs.count(), 1)
- self.assertGreaterEqual(str(qs.query).lower().count('extract'), 2)
-
-+ def test_extract_lookup_name_sql_injection(self):
-+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
-+ end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
-+ if settings.USE_TZ:
-+ start_datetime = timezone.make_aware(start_datetime)
-+ end_datetime = timezone.make_aware(end_datetime)
-+ self.create_model(start_datetime, end_datetime)
-+ self.create_model(end_datetime, start_datetime)
-+
-+ msg = "Invalid lookup_name: "
-+ with self.assertRaisesMessage(ValueError, msg):
-+ DTModel.objects.filter(
-+ start_datetime__year=Extract(
-+ "start_datetime", "day' FROM start_datetime)) OR 1=1;--"
-+ )
-+ ).exists()
-+
- def test_extract_func(self):
- start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
- end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
-@@ -620,6 +637,23 @@ def test_extract_second_func(self):
- )
- self.assertEqual(DTModel.objects.filter(start_datetime__second=ExtractSecond('start_datetime')).count(), 2)
-
-+ def test_trunc_lookup_name_sql_injection(self):
-+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
-+ end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
-+ if settings.USE_TZ:
-+ start_datetime = timezone.make_aware(start_datetime)
-+ end_datetime = timezone.make_aware(end_datetime)
-+ self.create_model(start_datetime, end_datetime)
-+ self.create_model(end_datetime, start_datetime)
-+ msg = "Invalid kind: "
-+ with self.assertRaisesMessage(ValueError, msg):
-+ DTModel.objects.filter(
-+ start_datetime__date=Trunc(
-+ "start_datetime",
-+ "year', start_datetime)) OR 1=1;--",
-+ )
-+ ).exists()
-+
- def test_trunc_func(self):
- start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
- end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
diff --git a/CVE-2023-23969.patch b/CVE-2023-23969.patch
deleted file mode 100644
index 13dc1bb1428f1fb3a2f59e5cd899821762a05c56..0000000000000000000000000000000000000000
--- a/CVE-2023-23969.patch
+++ /dev/null
@@ -1,100 +0,0 @@
-From 80353a42e41fd22933184a30f2e2c04d0c274c83 Mon Sep 17 00:00:00 2001
-From: starlet-dx <15929766099@163.com>
-Date: Mon, 13 Feb 2023 19:31:46 +0800
-Subject: [PATCH 1/1] [3.2.x] Fixed CVE-2023-23969 -- Prevented DoS with pathological values for Accept-Language.
-
-The parsed values of Accept-Language headers are cached in order to avoid repetitive parsing. This leads to a potential denial-of-service vector via excessive memory usage if the raw value of Accept-Language headers is very large.
-
-Accept-Language headers are now limited to a maximum length in order to avoid this issue.
----
- django/utils/translation/trans_real.py | 32 +++++++++++++++++++++++++-
- tests/i18n/tests.py | 12 ++++++++++
- 2 files changed, 43 insertions(+), 1 deletion(-)
-
-diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
-index 8042f6f..b262a50 100644
---- a/django/utils/translation/trans_real.py
-+++ b/django/utils/translation/trans_real.py
-@@ -30,6 +30,11 @@ _default = None
- # magic gettext number to separate context from message
- 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
-+
- # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9
- # and RFC 3066, section 2.1
- accept_language_re = _lazy_re_compile(r'''
-@@ -556,7 +561,7 @@ def get_language_from_request(request, check_path=False):
-
-
- @functools.lru_cache(maxsize=1000)
--def parse_accept_lang_header(lang_string):
-+def _parse_accept_lang_header(lang_string):
- """
- Parse the lang_string, which is the body of an HTTP Accept-Language
- header, and return a tuple of (lang, q-value), ordered by 'q' values.
-@@ -578,3 +583,28 @@ def parse_accept_lang_header(lang_string):
- result.append((lang, priority))
- result.sort(key=lambda k: k[1], reverse=True)
- return tuple(result)
-+
-+
-+def parse_accept_lang_header(lang_string):
-+ """
-+ Parse the value of the Accept-Language header up to a maximum length.
-+
-+ The value of the header is truncated to a maximum length to avoid potential
-+ denial of service and memory exhaustion attacks. Excessive memory could be
-+ used if the raw value is very large as it would be cached due to the use of
-+ 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:
-+ 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)
-+ if index > 0:
-+ return _parse_accept_lang_header(lang_string[:index])
-+
-+ # Don't attempt to parse if there is only one language-range value which is
-+ # longer than the maximum allowed length and so truncated.
-+ return ()
-diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
-index 7edceb1..f379f8f 100644
---- a/tests/i18n/tests.py
-+++ b/tests/i18n/tests.py
-@@ -1352,6 +1352,14 @@ class MiscTests(SimpleTestCase):
- ('de;q=0.', [('de', 0.0)]),
- ('en; q=1,', [('en', 1.0)]),
- ('en; q=1.0, * ; q=0.5', [('en', 1.0), ('*', 0.5)]),
-+ (
-+ 'en' + '-x' * 20,
-+ [('en-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x', 1.0)],
-+ ),
-+ (
-+ ', '.join(['en; q=1.0'] * 20),
-+ [('en', 1.0)] * 20,
-+ ),
- # Bad headers
- ('en-gb;q=1.0000', []),
- ('en;q=0.1234', []),
-@@ -1367,6 +1375,10 @@ class MiscTests(SimpleTestCase):
- ('12-345', []),
- ('', []),
- ('en;q=1e0', []),
-+ # Invalid as language-range value too long.
-+ ('xxxxxxxx' + '-xxxxxxxx' * 500, []),
-+ # Header value too long, only parse up to limit.
-+ (', '.join(['en; q=1.0'] * 500), [('en', 1.0)] * 45),
- ]
- for value, expected in tests:
- with self.subTest(value=value):
---
-2.30.0
-
diff --git a/CVE-2023-24580.patch b/CVE-2023-24580.patch
deleted file mode 100644
index 9f23a5499be925bf9981a635994e041bdd10e554..0000000000000000000000000000000000000000
--- a/CVE-2023-24580.patch
+++ /dev/null
@@ -1,401 +0,0 @@
-From a665ed5179f5bbd3db95ce67286d0192eff041d8 Mon Sep 17 00:00:00 2001
-From: Markus Holtermann
-Date: Tue, 13 Dec 2022 10:27:39 +0100
-Subject: [PATCH] [3.2.x] Fixed CVE-2023-24580 -- Prevented DoS with too many
- uploaded files.
-
-Thanks to Jakob Ackermann for the report.
----
- django/conf/global_settings.py | 4 ++
- django/core/exceptions.py | 9 +++
- django/core/handlers/exception.py | 4 +-
- django/http/multipartparser.py | 62 +++++++++++++++++----
- django/http/request.py | 6 +-
- docs/ref/exceptions.txt | 5 ++
- docs/ref/settings.txt | 23 ++++++++
- docs/releases/3.2.18.txt | 10 +++-
- tests/handlers/test_exception.py | 28 +++++++++-
- tests/requests/test_data_upload_settings.py | 51 ++++++++++++++++-
- 10 files changed, 184 insertions(+), 18 deletions(-)
-
-diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
-index cf9fae496e3a..4a27887a8f04 100644
---- a/django/conf/global_settings.py
-+++ b/django/conf/global_settings.py
-@@ -303,6 +303,10 @@ def gettext_noop(s):
- # SuspiciousOperation (TooManyFieldsSent) is raised.
- DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
-
-+# Maximum number of files encoded in a multipart upload that will be read
-+# before a SuspiciousOperation (TooManyFilesSent) is raised.
-+DATA_UPLOAD_MAX_NUMBER_FILES = 100
-+
- # Directory in which upload streamed files will be temporarily saved. A value of
- # `None` will make Django use the operating system's default temporary directory
- # (i.e. "/tmp" on *nix systems).
-diff --git a/django/core/exceptions.py b/django/core/exceptions.py
-index 673d004d5756..83161a58cd66 100644
---- a/django/core/exceptions.py
-+++ b/django/core/exceptions.py
-@@ -58,6 +58,15 @@ class TooManyFieldsSent(SuspiciousOperation):
- pass
-
-
-+class TooManyFilesSent(SuspiciousOperation):
-+ """
-+ The number of fields in a GET or POST request exceeded
-+ settings.DATA_UPLOAD_MAX_NUMBER_FILES.
-+ """
-+
-+ pass
-+
-+
- class RequestDataTooBig(SuspiciousOperation):
- """
- The size of the request (excluding any file uploads) exceeded
-diff --git a/django/core/handlers/exception.py b/django/core/handlers/exception.py
-index 3005a5eccb11..2ecc2a0fd697 100644
---- a/django/core/handlers/exception.py
-+++ b/django/core/handlers/exception.py
-@@ -9,7 +9,7 @@
- from django.core import signals
- from django.core.exceptions import (
- BadRequest, PermissionDenied, RequestDataTooBig, SuspiciousOperation,
-- TooManyFieldsSent,
-+ TooManyFieldsSent, TooManyFilesSent,
- )
- from django.http import Http404
- from django.http.multipartparser import MultiPartParserError
-@@ -88,7 +88,7 @@ def response_for_exception(request, exc):
- exc_info=sys.exc_info(),
- )
- elif isinstance(exc, SuspiciousOperation):
-- if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)):
-+ if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent)):
- # POST data can't be accessed again, otherwise the original
- # exception would be raised.
- request._mark_post_parse_error()
-diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
-index 35a54f4ca12e..d8a304d4babe 100644
---- a/django/http/multipartparser.py
-+++ b/django/http/multipartparser.py
-@@ -14,6 +14,7 @@
- from django.conf import settings
- from django.core.exceptions import (
- RequestDataTooBig, SuspiciousMultipartForm, TooManyFieldsSent,
-+ TooManyFilesSent,
- )
- from django.core.files.uploadhandler import (
- SkipFile, StopFutureHandlers, StopUpload,
-@@ -38,6 +39,7 @@ class InputStreamExhausted(Exception):
- RAW = "raw"
- FILE = "file"
- FIELD = "field"
-+FIELD_TYPES = frozenset([FIELD, RAW])
-
-
- class MultiPartParser:
-@@ -102,6 +104,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None):
- self._upload_handlers = upload_handlers
-
- def parse(self):
-+ # Call the actual parse routine and close all open files in case of
-+ # errors. This is needed because if exceptions are thrown the
-+ # MultiPartParser will not be garbage collected immediately and
-+ # resources would be kept alive. This is only needed for errors because
-+ # the Request object closes all uploaded files at the end of the
-+ # request.
-+ try:
-+ return self._parse()
-+ except Exception:
-+ if hasattr(self, "_files"):
-+ for _, files in self._files.lists():
-+ for fileobj in files:
-+ fileobj.close()
-+ raise
-+
-+ def _parse(self):
- """
- Parse the POST data and break it into a FILES MultiValueDict and a POST
- MultiValueDict.
-@@ -147,6 +165,8 @@ def parse(self):
- num_bytes_read = 0
- # To count the number of keys in the request.
- num_post_keys = 0
-+ # To count the number of files in the request.
-+ num_files = 0
- # To limit the amount of data read from the request.
- read_size = None
- # Whether a file upload is finished.
-@@ -162,6 +182,20 @@ def parse(self):
- old_field_name = None
- uploaded_file = True
-
-+ if (
-+ item_type in FIELD_TYPES and
-+ settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None
-+ ):
-+ # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS.
-+ num_post_keys += 1
-+ # 2 accounts for empty raw fields before and after the
-+ # last boundary.
-+ if settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys:
-+ raise TooManyFieldsSent(
-+ "The number of GET/POST parameters exceeded "
-+ "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS."
-+ )
-+
- try:
- disposition = meta_data['content-disposition'][1]
- field_name = disposition['name'].strip()
-@@ -174,15 +208,6 @@ def parse(self):
- field_name = force_str(field_name, encoding, errors='replace')
-
- if item_type == FIELD:
-- # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS.
-- num_post_keys += 1
-- if (settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None and
-- settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys):
-- raise TooManyFieldsSent(
-- 'The number of GET/POST parameters exceeded '
-- 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.'
-- )
--
- # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE.
- if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None:
- read_size = settings.DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read
-@@ -208,6 +233,16 @@ def parse(self):
-
- self._post.appendlist(field_name, force_str(data, encoding, errors='replace'))
- elif item_type == FILE:
-+ # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FILES.
-+ num_files += 1
-+ if (
-+ settings.DATA_UPLOAD_MAX_NUMBER_FILES is not None and
-+ num_files > settings.DATA_UPLOAD_MAX_NUMBER_FILES
-+ ):
-+ raise TooManyFilesSent(
-+ "The number of files exceeded "
-+ "settings.DATA_UPLOAD_MAX_NUMBER_FILES."
-+ )
- # This is a file, use the handler...
- file_name = disposition.get('filename')
- if file_name:
-@@ -276,8 +311,13 @@ def parse(self):
- # Handle file upload completions on next iteration.
- old_field_name = field_name
- else:
-- # If this is neither a FIELD or a FILE, just exhaust the stream.
-- exhaust(stream)
-+ # If this is neither a FIELD nor a FILE, exhaust the field
-+ # stream. Note: There could be an error here at some point,
-+ # but there will be at least two RAW types (before and
-+ # after the other boundaries). This branch is usually not
-+ # reached at all, because a missing content-disposition
-+ # header will skip the whole boundary.
-+ exhaust(field_stream)
- except StopUpload as e:
- self._close_files()
- if not e.connection_reset:
-diff --git a/django/http/request.py b/django/http/request.py
-index 195341ec4b69..b6cd7a372f14 100644
---- a/django/http/request.py
-+++ b/django/http/request.py
-@@ -12,7 +12,9 @@
- DisallowedHost, ImproperlyConfigured, RequestDataTooBig, TooManyFieldsSent,
- )
- from django.core.files import uploadhandler
--from django.http.multipartparser import MultiPartParser, MultiPartParserError
-+from django.http.multipartparser import (
-+ MultiPartParser, MultiPartParserError, TooManyFilesSent,
-+)
- from django.utils.datastructures import (
- CaseInsensitiveMapping, ImmutableList, MultiValueDict,
- )
-@@ -360,7 +362,7 @@ def _load_post_and_files(self):
- data = self
- try:
- self._post, self._files = self.parse_file_upload(self.META, data)
-- except MultiPartParserError:
-+ except (MultiPartParserError, TooManyFilesSent):
- # An error occurred while parsing POST data. Since when
- # formatting the error the request handler might access
- # self.POST, set self._post and self._file to prevent
-diff --git a/docs/ref/exceptions.txt b/docs/ref/exceptions.txt
-index 2f5aa64b9d9d..7d34025cd65c 100644
---- a/docs/ref/exceptions.txt
-+++ b/docs/ref/exceptions.txt
-@@ -84,12 +84,17 @@ Django core exception classes are defined in ``django.core.exceptions``.
- * ``SuspiciousMultipartForm``
- * ``SuspiciousSession``
- * ``TooManyFieldsSent``
-+ * ``TooManyFilesSent``
-
- If a ``SuspiciousOperation`` exception reaches the ASGI/WSGI handler level
- it is logged at the ``Error`` level and results in
- a :class:`~django.http.HttpResponseBadRequest`. See the :doc:`logging
- documentation ` for more information.
-
-+.. versionchanged:: 3.2.18
-+
-+ ``SuspiciousOperation`` is raised when too many files are submitted.
-+
- ``PermissionDenied``
- --------------------
-
-diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
-index 9bfadbc89bd2..9173009c94d5 100644
---- a/docs/ref/settings.txt
-+++ b/docs/ref/settings.txt
-@@ -1063,6 +1063,28 @@ could be used as a denial-of-service attack vector if left unchecked. Since web
- servers don't typically perform deep request inspection, it's not possible to
- perform a similar check at that level.
-
-+.. setting:: DATA_UPLOAD_MAX_NUMBER_FILES
-+
-+``DATA_UPLOAD_MAX_NUMBER_FILES``
-+--------------------------------
-+
-+.. versionadded:: 3.2.18
-+
-+Default: ``100``
-+
-+The maximum number of files that may be received via POST in a
-+``multipart/form-data`` encoded request before a
-+:exc:`~django.core.exceptions.SuspiciousOperation` (``TooManyFiles``) is
-+raised. You can set this to ``None`` to disable the check. Applications that
-+are expected to receive an unusually large number of file fields should tune
-+this setting.
-+
-+The number of accepted files is correlated to the amount of time and memory
-+needed to process the request. Large requests could be used as a
-+denial-of-service attack vector if left unchecked. Since web servers don't
-+typically perform deep request inspection, it's not possible to perform a
-+similar check at that level.
-+
- .. setting:: DATABASE_ROUTERS
-
- ``DATABASE_ROUTERS``
-@@ -3671,6 +3693,7 @@ HTTP
- ----
- * :setting:`DATA_UPLOAD_MAX_MEMORY_SIZE`
- * :setting:`DATA_UPLOAD_MAX_NUMBER_FIELDS`
-+* :setting:`DATA_UPLOAD_MAX_NUMBER_FILES`
- * :setting:`DEFAULT_CHARSET`
- * :setting:`DISALLOWED_USER_AGENTS`
- * :setting:`FORCE_SCRIPT_NAME`
-diff --git a/tests/handlers/test_exception.py b/tests/handlers/test_exception.py
-index 0c1e76399045..7de2edaeea34 100644
---- a/tests/handlers/test_exception.py
-+++ b/tests/handlers/test_exception.py
-@@ -1,6 +1,8 @@
- from django.core.handlers.wsgi import WSGIHandler
- from django.test import SimpleTestCase, override_settings
--from django.test.client import FakePayload
-+from django.test.client import (
-+ BOUNDARY, MULTIPART_CONTENT, FakePayload, encode_multipart,
-+)
-
-
- class ExceptionHandlerTests(SimpleTestCase):
-@@ -25,3 +27,27 @@ def test_data_upload_max_memory_size_exceeded(self):
- def test_data_upload_max_number_fields_exceeded(self):
- response = WSGIHandler()(self.get_suspicious_environ(), lambda *a, **k: None)
- self.assertEqual(response.status_code, 400)
-+
-+ @override_settings(DATA_UPLOAD_MAX_NUMBER_FILES=2)
-+ def test_data_upload_max_number_files_exceeded(self):
-+ payload = FakePayload(
-+ encode_multipart(
-+ BOUNDARY,
-+ {
-+ "a.txt": "Hello World!",
-+ "b.txt": "Hello Django!",
-+ "c.txt": "Hello Python!",
-+ },
-+ )
-+ )
-+ environ = {
-+ "REQUEST_METHOD": "POST",
-+ "CONTENT_TYPE": MULTIPART_CONTENT,
-+ "CONTENT_LENGTH": len(payload),
-+ "wsgi.input": payload,
-+ "SERVER_NAME": "test",
-+ "SERVER_PORT": "8000",
-+ }
-+
-+ response = WSGIHandler()(environ, lambda *a, **k: None)
-+ self.assertEqual(response.status_code, 400)
-diff --git a/tests/requests/test_data_upload_settings.py b/tests/requests/test_data_upload_settings.py
-index 44897cc9fa97..ded778b42286 100644
---- a/tests/requests/test_data_upload_settings.py
-+++ b/tests/requests/test_data_upload_settings.py
-@@ -1,11 +1,14 @@
- from io import BytesIO
-
--from django.core.exceptions import RequestDataTooBig, TooManyFieldsSent
-+from django.core.exceptions import (
-+ RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent,
-+)
- from django.core.handlers.wsgi import WSGIRequest
- from django.test import SimpleTestCase
- from django.test.client import FakePayload
-
- TOO_MANY_FIELDS_MSG = 'The number of GET/POST parameters exceeded settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.'
-+TOO_MANY_FILES_MSG = 'The number of files exceeded settings.DATA_UPLOAD_MAX_NUMBER_FILES.'
- TOO_MUCH_DATA_MSG = 'Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.'
-
-
-@@ -166,6 +169,52 @@ def test_no_limit(self):
- self.request._load_post_and_files()
-
-
-+class DataUploadMaxNumberOfFilesMultipartPost(SimpleTestCase):
-+ def setUp(self):
-+ payload = FakePayload(
-+ "\r\n".join(
-+ [
-+ "--boundary",
-+ (
-+ 'Content-Disposition: form-data; name="name1"; '
-+ 'filename="name1.txt"'
-+ ),
-+ "",
-+ "value1",
-+ "--boundary",
-+ (
-+ 'Content-Disposition: form-data; name="name2"; '
-+ 'filename="name2.txt"'
-+ ),
-+ "",
-+ "value2",
-+ "--boundary--",
-+ ]
-+ )
-+ )
-+ self.request = WSGIRequest(
-+ {
-+ "REQUEST_METHOD": "POST",
-+ "CONTENT_TYPE": "multipart/form-data; boundary=boundary",
-+ "CONTENT_LENGTH": len(payload),
-+ "wsgi.input": payload,
-+ }
-+ )
-+
-+ def test_number_exceeded(self):
-+ with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=1):
-+ with self.assertRaisesMessage(TooManyFilesSent, TOO_MANY_FILES_MSG):
-+ self.request._load_post_and_files()
-+
-+ def test_number_not_exceeded(self):
-+ with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=2):
-+ self.request._load_post_and_files()
-+
-+ def test_no_limit(self):
-+ with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=None):
-+ self.request._load_post_and_files()
-+
-+
- class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase):
- def setUp(self):
- payload = FakePayload("\r\n".join(['a=1&a=2&a=3', '']))
diff --git a/CVE-2023-31047.patch b/CVE-2023-31047.patch
deleted file mode 100644
index bdcf26b8987a65ef9e6052269892893840c09bfa..0000000000000000000000000000000000000000
--- a/CVE-2023-31047.patch
+++ /dev/null
@@ -1,322 +0,0 @@
-From 6bb2e1ac607b1a399e1d7bd3650c04a586e6746e Mon Sep 17 00:00:00 2001
-From: starlet-dx <15929766099@163.com>
-Date: Tue, 16 May 2023 10:00:42 +0800
-Subject: [PATCH 1/1] [3.2.x] Fixed CVE-2023-31047, Fixed #31710 -- Prevented
- potential bypass of validation when uploading multiple files using one form
- field.
-
-Thanks Moataz Al-Sharida and nawaik for reports.
-
-Co-authored-by: Shai Berger
-Co-authored-by: nessita <124304+nessita@users.noreply.github.com>
-
-Origin:
-https://github.com/django/django/commit/eed53d0011622e70b936e203005f0e6f4ac48965
----
- django/forms/widgets.py | 26 ++++++-
- docs/topics/http/file-uploads.txt | 65 ++++++++++++++++--
- .../forms_tests/field_tests/test_filefield.py | 68 ++++++++++++++++++-
- .../widget_tests/test_clearablefileinput.py | 5 ++
- .../widget_tests/test_fileinput.py | 44 ++++++++++++
- 5 files changed, 200 insertions(+), 8 deletions(-)
-
-diff --git a/django/forms/widgets.py b/django/forms/widgets.py
-index 1b1c143..8ef8255 100644
---- a/django/forms/widgets.py
-+++ b/django/forms/widgets.py
-@@ -378,16 +378,40 @@ class MultipleHiddenInput(HiddenInput):
-
- class FileInput(Input):
- input_type = 'file'
-+ allow_multiple_selected = False
- needs_multipart_form = True
- template_name = 'django/forms/widgets/file.html'
-
-+ def __init__(self, attrs=None):
-+ if (
-+ attrs is not None and
-+ not self.allow_multiple_selected and
-+ attrs.get("multiple", False)
-+ ):
-+ raise ValueError(
-+ "%s doesn't support uploading multiple files."
-+ % self.__class__.__qualname__
-+ )
-+ if self.allow_multiple_selected:
-+ if attrs is None:
-+ attrs = {"multiple": True}
-+ else:
-+ attrs.setdefault("multiple", True)
-+ super().__init__(attrs)
-+
- def format_value(self, value):
- """File input never renders a value."""
- return
-
- def value_from_datadict(self, data, files, name):
- "File widgets take data from FILES, not POST"
-- return files.get(name)
-+ getter = files.get
-+ if self.allow_multiple_selected:
-+ try:
-+ getter = files.getlist
-+ except AttributeError:
-+ pass
-+ return getter(name)
-
- def value_omitted_from_data(self, data, files, name):
- return name not in files
-diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
-index ca272d7..4388594 100644
---- a/docs/topics/http/file-uploads.txt
-+++ b/docs/topics/http/file-uploads.txt
-@@ -126,19 +126,54 @@ model::
- form = UploadFileForm()
- return render(request, 'upload.html', {'form': form})
-
-+.. _uploading_multiple_files:
-+
- Uploading multiple files
- ------------------------
-
--If you want to upload multiple files using one form field, set the ``multiple``
--HTML attribute of field's widget:
-+..
-+ Tests in tests.forms_tests.field_tests.test_filefield.MultipleFileFieldTest
-+ should be updated after any changes in the following snippets.
-+
-+If you want to upload multiple files using one form field, create a subclass
-+of the field's widget and set the ``allow_multiple_selected`` attribute on it
-+to ``True``.
-+
-+In order for such files to be all validated by your form (and have the value of
-+the field include them all), you will also have to subclass ``FileField``. See
-+below for an example.
-+
-+.. admonition:: Multiple file field
-+
-+ Django is likely to have a proper multiple file field support at some point
-+ in the future.
-
- .. code-block:: python
- :caption: forms.py
-
- from django import forms
-
-+
-+ class MultipleFileInput(forms.ClearableFileInput):
-+ allow_multiple_selected = True
-+
-+
-+ class MultipleFileField(forms.FileField):
-+ def __init__(self, *args, **kwargs):
-+ kwargs.setdefault("widget", MultipleFileInput())
-+ super().__init__(*args, **kwargs)
-+
-+ def clean(self, data, initial=None):
-+ single_file_clean = super().clean
-+ if isinstance(data, (list, tuple)):
-+ result = [single_file_clean(d, initial) for d in data]
-+ else:
-+ result = single_file_clean(data, initial)
-+ return result
-+
-+
- class FileFieldForm(forms.Form):
-- file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
-+ file_field = MultipleFileField()
-
- Then override the ``post`` method of your
- :class:`~django.views.generic.edit.FormView` subclass to handle multiple file
-@@ -158,14 +193,32 @@ uploads:
- def post(self, request, *args, **kwargs):
- form_class = self.get_form_class()
- form = self.get_form(form_class)
-- files = request.FILES.getlist('file_field')
- if form.is_valid():
-- for f in files:
-- ... # Do something with each file.
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
-+ def form_valid(self, form):
-+ files = form.cleaned_data["file_field"]
-+ for f in files:
-+ ... # Do something with each file.
-+ return super().form_valid()
-+
-+.. warning::
-+
-+ This will allow you to handle multiple files at the form level only. Be
-+ aware that you cannot use it to put multiple files on a single model
-+ instance (in a single field), for example, even if the custom widget is used
-+ with a form field related to a model ``FileField``.
-+
-+.. versionchanged:: 3.2.19
-+
-+ In previous versions, there was no support for the ``allow_multiple_selected``
-+ class attribute, and users were advised to create the widget with the HTML
-+ attribute ``multiple`` set through the ``attrs`` argument. However, this
-+ caused validation of the form field to be applied only to the last file
-+ submitted, which could have adverse security implications.
-+
- Upload Handlers
- ===============
-
-diff --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py
-index 2db106e..b54febd 100644
---- a/tests/forms_tests/field_tests/test_filefield.py
-+++ b/tests/forms_tests/field_tests/test_filefield.py
-@@ -2,7 +2,8 @@ import pickle
-
- from django.core.exceptions import ValidationError
- from django.core.files.uploadedfile import SimpleUploadedFile
--from django.forms import FileField
-+from django.core.validators import validate_image_file_extension
-+from django.forms import FileField, FileInput
- from django.test import SimpleTestCase
-
-
-@@ -83,3 +84,68 @@ class FileFieldTest(SimpleTestCase):
-
- def test_file_picklable(self):
- self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField)
-+
-+
-+class MultipleFileInput(FileInput):
-+ allow_multiple_selected = True
-+
-+
-+class MultipleFileField(FileField):
-+ def __init__(self, *args, **kwargs):
-+ kwargs.setdefault("widget", MultipleFileInput())
-+ super().__init__(*args, **kwargs)
-+
-+ def clean(self, data, initial=None):
-+ single_file_clean = super().clean
-+ if isinstance(data, (list, tuple)):
-+ result = [single_file_clean(d, initial) for d in data]
-+ else:
-+ result = single_file_clean(data, initial)
-+ return result
-+
-+
-+class MultipleFileFieldTest(SimpleTestCase):
-+ def test_file_multiple(self):
-+ f = MultipleFileField()
-+ files = [
-+ SimpleUploadedFile("name1", b"Content 1"),
-+ SimpleUploadedFile("name2", b"Content 2"),
-+ ]
-+ self.assertEqual(f.clean(files), files)
-+
-+ def test_file_multiple_empty(self):
-+ f = MultipleFileField()
-+ files = [
-+ SimpleUploadedFile("empty", b""),
-+ SimpleUploadedFile("nonempty", b"Some Content"),
-+ ]
-+ msg = "'The submitted file is empty.'"
-+ with self.assertRaisesMessage(ValidationError, msg):
-+ f.clean(files)
-+ with self.assertRaisesMessage(ValidationError, msg):
-+ f.clean(files[::-1])
-+
-+ def test_file_multiple_validation(self):
-+ f = MultipleFileField(validators=[validate_image_file_extension])
-+
-+ good_files = [
-+ SimpleUploadedFile("image1.jpg", b"fake JPEG"),
-+ SimpleUploadedFile("image2.png", b"faux image"),
-+ SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"),
-+ ]
-+ self.assertEqual(f.clean(good_files), good_files)
-+
-+ evil_files = [
-+ SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"),
-+ SimpleUploadedFile("image2.png", b"faux image"),
-+ SimpleUploadedFile("image3.jpg", b"fake JPEG"),
-+ ]
-+
-+ evil_rotations = (
-+ evil_files[i:] + evil_files[:i] # Rotate by i.
-+ for i in range(len(evil_files))
-+ )
-+ msg = "File extension “sh” is not allowed. Allowed extensions are: "
-+ for rotated_evil_files in evil_rotations:
-+ with self.assertRaisesMessage(ValidationError, msg):
-+ f.clean(rotated_evil_files)
-diff --git a/tests/forms_tests/widget_tests/test_clearablefileinput.py b/tests/forms_tests/widget_tests/test_clearablefileinput.py
-index dee44c4..6cf1476 100644
---- a/tests/forms_tests/widget_tests/test_clearablefileinput.py
-+++ b/tests/forms_tests/widget_tests/test_clearablefileinput.py
-@@ -176,3 +176,8 @@ class ClearableFileInputTest(WidgetTest):
- self.assertIs(widget.value_omitted_from_data({}, {}, 'field'), True)
- self.assertIs(widget.value_omitted_from_data({}, {'field': 'x'}, 'field'), False)
- self.assertIs(widget.value_omitted_from_data({'field-clear': 'y'}, {}, 'field'), False)
-+
-+ def test_multiple_error(self):
-+ msg = "ClearableFileInput doesn't support uploading multiple files."
-+ with self.assertRaisesMessage(ValueError, msg):
-+ ClearableFileInput(attrs={"multiple": True})
-diff --git a/tests/forms_tests/widget_tests/test_fileinput.py b/tests/forms_tests/widget_tests/test_fileinput.py
-index 8eec262..8068f70 100644
---- a/tests/forms_tests/widget_tests/test_fileinput.py
-+++ b/tests/forms_tests/widget_tests/test_fileinput.py
-@@ -1,4 +1,6 @@
-+from django.core.files.uploadedfile import SimpleUploadedFile
- from django.forms import FileInput
-+from django.utils.datastructures import MultiValueDict
-
- from .base import WidgetTest
-
-@@ -24,3 +26,45 @@ class FileInputTest(WidgetTest):
- # user to keep the existing, initial value.
- self.assertIs(self.widget.use_required_attribute(None), True)
- self.assertIs(self.widget.use_required_attribute('resume.txt'), False)
-+
-+ def test_multiple_error(self):
-+ msg = "FileInput doesn't support uploading multiple files."
-+ with self.assertRaisesMessage(ValueError, msg):
-+ FileInput(attrs={"multiple": True})
-+
-+ def test_value_from_datadict_multiple(self):
-+ class MultipleFileInput(FileInput):
-+ allow_multiple_selected = True
-+
-+ file_1 = SimpleUploadedFile("something1.txt", b"content 1")
-+ file_2 = SimpleUploadedFile("something2.txt", b"content 2")
-+ # Uploading multiple files is allowed.
-+ widget = MultipleFileInput(attrs={"multiple": True})
-+ value = widget.value_from_datadict(
-+ data={"name": "Test name"},
-+ files=MultiValueDict({"myfile": [file_1, file_2]}),
-+ name="myfile",
-+ )
-+ self.assertEqual(value, [file_1, file_2])
-+ # Uploading multiple files is not allowed.
-+ widget = FileInput()
-+ value = widget.value_from_datadict(
-+ data={"name": "Test name"},
-+ files=MultiValueDict({"myfile": [file_1, file_2]}),
-+ name="myfile",
-+ )
-+ self.assertEqual(value, file_2)
-+
-+ def test_multiple_default(self):
-+ class MultipleFileInput(FileInput):
-+ allow_multiple_selected = True
-+
-+ tests = [
-+ (None, True),
-+ ({"class": "myclass"}, True),
-+ ({"multiple": False}, False),
-+ ]
-+ for attrs, expected in tests:
-+ with self.subTest(attrs=attrs):
-+ widget = MultipleFileInput(attrs=attrs)
-+ self.assertIs(widget.attrs["multiple"], expected)
---
-2.30.0
-
diff --git a/CVE-2023-36053.patch b/CVE-2023-36053.patch
deleted file mode 100644
index e0dc2aee91b335e499eb897dfb82475df68c00ac..0000000000000000000000000000000000000000
--- a/CVE-2023-36053.patch
+++ /dev/null
@@ -1,244 +0,0 @@
-From 454f2fb93437f98917283336201b4048293f7582 Mon Sep 17 00:00:00 2001
-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 ++++++++++++++++++-
- docs/releases/3.2.20.txt | 7 +++++-
- .../field_tests/test_emailfield.py | 5 +++-
- tests/forms_tests/tests/test_forms.py | 19 +++++++++-----
- tests/validators/tests.py | 11 ++++++++
- 8 files changed, 72 insertions(+), 12 deletions(-)
-
-diff --git a/django/core/validators.py b/django/core/validators.py
-index 731ccf2d4690..b9b58dfa6176 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 @@ def __init__(self, schemes=None, **kwargs):
- 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})
-@@ -210,7 +211,9 @@ def __init__(self, message=None, code=None, allowlist=None, *, whitelist=None):
- 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 SignupForm(Form):
- 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 @@ class Person(Form):
- Yes
- No
-
--Email:
-+Email:
-
- Age: """
- )
-@@ -2840,7 +2847,7 @@ class Person(Form):
- Yes
- No
-
--Email:
-+Email:
-
- Age:
-
"""
-@@ -2859,7 +2866,7 @@ class Person(Form):
- No
-
- Email:
--
-+
- Age:
-
- """
-@@ -3489,7 +3496,7 @@ class CommentForm(Form):
- 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 @@
-
- (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 @@
- (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 --git a/CVE-2023-41164.patch b/CVE-2023-41164.patch
deleted file mode 100644
index ad4bd06c8699a093321834ef337f6cbfae1dd30e..0000000000000000000000000000000000000000
--- a/CVE-2023-41164.patch
+++ /dev/null
@@ -1,83 +0,0 @@
-From 6f030b1149bd8fa4ba90452e77cb3edc095ce54e Mon Sep 17 00:00:00 2001
-From: Mariusz Felisiak
-Date: Tue, 22 Aug 2023 08:53:03 +0200
-Subject: [PATCH] [3.2.x] Fixed CVE-2023-41164 -- Fixed potential DoS in
- django.utils.encoding.uri_to_iri().
-
-Thanks MProgrammer (https://hackerone.com/mprogrammer) for the report.
-
-Origin: https://github.com/django/django/commit/6f030b1149bd8fa4ba90452e77cb3edc095ce54e
-
-Co-authored-by: nessita <124304+nessita@users.noreply.github.com>
----
- django/utils/encoding.py | 6 ++++--
- docs/releases/3.2.21.txt | 7 ++++++-
- tests/utils_tests/test_encoding.py | 21 ++++++++++++++++++++-
- 3 files changed, 30 insertions(+), 4 deletions(-)
-
-diff --git a/django/utils/encoding.py b/django/utils/encoding.py
-index e1ebacef4705..c5c4463b1c22 100644
---- a/django/utils/encoding.py
-+++ b/django/utils/encoding.py
-@@ -229,6 +229,7 @@ def repercent_broken_unicode(path):
- repercent-encode any octet produced that is not part of a strictly legal
- UTF-8 octet sequence.
- """
-+ changed_parts = []
- while True:
- try:
- path.decode()
-@@ -236,9 +237,10 @@ def repercent_broken_unicode(path):
- # CVE-2019-14235: A recursion shouldn't be used since the exception
- # handling uses massive amounts of memory
- repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~")
-- path = path[:e.start] + repercent.encode() + path[e.end:]
-+ changed_parts.append(path[:e.start] + repercent.encode())
-+ path = path[e.end:]
- else:
-- return path
-+ return b"".join(changed_parts) + path
-
-
- def filepath_to_uri(path):
-diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py
-index 36f2d8665f3c..42779050cb3a 100644
---- a/tests/utils_tests/test_encoding.py
-+++ b/tests/utils_tests/test_encoding.py
-@@ -1,9 +1,10 @@
- import datetime
-+import inspect
- import sys
- import unittest
- from pathlib import Path
- from unittest import mock
--from urllib.parse import quote_plus
-+from urllib.parse import quote, quote_plus
-
- from django.test import SimpleTestCase
- from django.utils.encoding import (
-@@ -101,6 +102,24 @@ def test_repercent_broken_unicode_recursion_error(self):
- except RecursionError:
- self.fail('Unexpected RecursionError raised.')
-
-+ def test_repercent_broken_unicode_small_fragments(self):
-+ data = b"test\xfctest\xfctest\xfc"
-+ decoded_paths = []
-+
-+ def mock_quote(*args, **kwargs):
-+ # The second frame is the call to repercent_broken_unicode().
-+ decoded_paths.append(inspect.currentframe().f_back.f_locals["path"])
-+ return quote(*args, **kwargs)
-+
-+ with mock.patch("django.utils.encoding.quote", mock_quote):
-+ self.assertEqual(repercent_broken_unicode(data), b"test%FCtest%FCtest%FC")
-+
-+ # decode() is called on smaller fragment of the path each time.
-+ self.assertEqual(
-+ decoded_paths,
-+ [b"test\xfctest\xfctest\xfc", b"test\xfctest\xfc", b"test\xfc"],
-+ )
-+
-
- class TestRFC3987IEncodingUtils(unittest.TestCase):
-
diff --git a/CVE-2023-43665.patch b/CVE-2023-43665.patch
deleted file mode 100644
index 523934e792255f63dbf6fd4ae0e75fc8010e4110..0000000000000000000000000000000000000000
--- a/CVE-2023-43665.patch
+++ /dev/null
@@ -1,168 +0,0 @@
-From ccdade1a0262537868d7ca64374de3d957ca50c5 Mon Sep 17 00:00:00 2001
-From: Natalia <124304+nessita@users.noreply.github.com>
-Date: Tue, 19 Sep 2023 09:51:48 -0300
-Subject: [PATCH] [3.2.x] Fixed CVE-2023-43665 -- Mitigated potential DoS in
- django.utils.text.Truncator when truncating HTML text.
-
-Thanks Wenchao Li of Alibaba Group for the report.
-
-Origin:
-https://github.com/django/django/commit/ccdade1a0262537868d7ca64374de3d957ca50c5
----
- django/utils/text.py | 18 ++++++++++++++++-
- docs/ref/templates/builtins.txt | 20 +++++++++++++++++++
- tests/utils_tests/test_text.py | 35 ++++++++++++++++++++++++---------
- 3 files changed, 63 insertions(+), 10 deletions(-)
-
-diff --git a/django/utils/text.py b/django/utils/text.py
-index baa44f2..83e258f 100644
---- a/django/utils/text.py
-+++ b/django/utils/text.py
-@@ -60,7 +60,14 @@ def wrap(text, width):
- class Truncator(SimpleLazyObject):
- """
- An object used to truncate text, either by characters or words.
-+
-+ When truncating HTML text (either chars or words), input will be limited to
-+ at most `MAX_LENGTH_HTML` characters.
- """
-+
-+ # 5 million characters are approximately 4000 text pages or 3 web pages.
-+ MAX_LENGTH_HTML = 5_000_000
-+
- def __init__(self, text):
- super().__init__(lambda: str(text))
-
-@@ -157,6 +164,11 @@ class Truncator(SimpleLazyObject):
- if words and length <= 0:
- return ''
-
-+ size_limited = False
-+ if len(text) > self.MAX_LENGTH_HTML:
-+ text = text[: self.MAX_LENGTH_HTML]
-+ size_limited = True
-+
- html4_singlets = (
- 'br', 'col', 'link', 'base', 'img',
- 'param', 'area', 'hr', 'input'
-@@ -206,10 +218,14 @@ class Truncator(SimpleLazyObject):
- # Add it to the start of the open tags list
- open_tags.insert(0, tagname)
-
-+ truncate_text = self.add_truncation_text("", truncate)
-+
- if current_len <= length:
-+ if size_limited and truncate_text:
-+ text += truncate_text
- return text
-+
- out = text[:end_text_pos]
-- truncate_text = self.add_truncation_text('', truncate)
- if truncate_text:
- out += truncate_text
- # Close any tags still open
-diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
-index 22509a2..a6fd971 100644
---- a/docs/ref/templates/builtins.txt
-+++ b/docs/ref/templates/builtins.txt
-@@ -2348,6 +2348,16 @@ If ``value`` is ``"Joel is a slug
"``, the output will be
-
- Newlines in the HTML content will be preserved.
-
-+.. admonition:: Size of input string
-+
-+ Processing large, potentially malformed HTML strings can be
-+ resource-intensive and impact service performance. ``truncatechars_html``
-+ limits input to the first five million characters.
-+
-+.. versionchanged:: 3.2.22
-+
-+ In older versions, strings over five million characters were processed.
-+
- .. templatefilter:: truncatewords
-
- ``truncatewords``
-@@ -2386,6 +2396,16 @@ If ``value`` is ``"Joel is a slug
"``, the output will be
-
- Newlines in the HTML content will be preserved.
-
-+.. admonition:: Size of input string
-+
-+ Processing large, potentially malformed HTML strings can be
-+ resource-intensive and impact service performance. ``truncatewords_html``
-+ limits input to the first five million characters.
-+
-+.. versionchanged:: 3.2.22
-+
-+ In older versions, strings over five million characters were processed.
-+
- .. templatefilter:: unordered_list
-
- ``unordered_list``
-diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
-index d2a94fc..0a6f0bc 100644
---- a/tests/utils_tests/test_text.py
-+++ b/tests/utils_tests/test_text.py
-@@ -1,5 +1,6 @@
- import json
- import sys
-+from unittest.mock import patch
-
- from django.core.exceptions import SuspiciousFileOperation
- from django.test import SimpleTestCase, ignore_warnings
-@@ -90,11 +91,17 @@ class TestUtilsText(SimpleTestCase):
- # lazy strings are handled correctly
- self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(10), 'The quick…')
-
-- def test_truncate_chars_html(self):
-+ @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
-+ def test_truncate_chars_html_size_limit(self):
-+ max_len = text.Truncator.MAX_LENGTH_HTML
-+ bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
-+ valid_html = "Joel is a slug
" # 14 chars
- perf_test_values = [
-- (('', None),
-- ('&' * 50000, '&' * 9 + '…'),
-- ('_X<<<<<<<<<<<>', None),
-+ ("", None),
-+ ("", "", None),
-+ (valid_html * bigger_len, "Joel is a…
"), # 10 chars
- ]
- for value, expected in perf_test_values:
- with self.subTest(value=value):
-@@ -152,15 +159,25 @@ class TestUtilsText(SimpleTestCase):
- truncator = text.Truncator('I <3 python, what about you?
')
- self.assertEqual('I <3 python,…
', truncator.words(3, html=True))
-
-+ @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
-+ def test_truncate_words_html_size_limit(self):
-+ max_len = text.Truncator.MAX_LENGTH_HTML
-+ bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
-+ valid_html = "Joel is a slug
" # 4 words
- perf_test_values = [
-- ('',
-- '&' * 50000,
-- '_X<<<<<<<<<<<>',
-+ ("", None),
-+ ("", "", None),
-+ (valid_html * bigger_len, valid_html * 12 + "Joel is…
"), # 50 words
- ]
-- for value in perf_test_values:
-+ for value, expected in perf_test_values:
- with self.subTest(value=value):
- truncator = text.Truncator(value)
-- self.assertEqual(value, truncator.words(50, html=True))
-+ self.assertEqual(
-+ expected if expected else value, truncator.words(50, html=True)
-+ )
-
- def test_wrap(self):
- digits = '1234 67 9'
---
-2.30.0
-
diff --git a/CVE-2023-46695.patch b/CVE-2023-46695.patch
deleted file mode 100644
index 30f6c6ff13aabe3bfd23b5d3baea2b7652aac0e3..0000000000000000000000000000000000000000
--- a/CVE-2023-46695.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-From f9a7fb8466a7ba4857eaf930099b5258f3eafb2b Mon Sep 17 00:00:00 2001
-From: Mariusz Felisiak
-Date: Tue, 17 Oct 2023 11:48:32 +0200
-Subject: [PATCH] [3.2.x] Fixed CVE-2023-46695 -- Fixed potential DoS in
- UsernameField on Windows.
-
-Thanks MProgrammer (https://hackerone.com/mprogrammer) for the report.
----
- django/contrib/auth/forms.py | 10 +++++++++-
- tests/auth_tests/test_forms.py | 8 +++++++-
- 2 files changed, 16 insertions(+), 2 deletions(-)
-
-diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
-index 20d8922..fb7cfda 100644
---- a/django/contrib/auth/forms.py
-+++ b/django/contrib/auth/forms.py
-@@ -62,7 +62,15 @@ class ReadOnlyPasswordHashField(forms.Field):
-
- class UsernameField(forms.CharField):
- def to_python(self, value):
-- return unicodedata.normalize('NFKC', super().to_python(value))
-+ value = super().to_python(value)
-+ if self.max_length is not None and len(value) > self.max_length:
-+ # Normalization can increase the string length (e.g.
-+ # "ff" -> "ff", "½" -> "1⁄2") but cannot reduce it, so there is no
-+ # point in normalizing invalid data. Moreover, Unicode
-+ # normalization is very slow on Windows and can be a DoS attack
-+ # vector.
-+ return value
-+ return unicodedata.normalize("NFKC", value)
-
- def widget_attrs(self, widget):
- return {
-diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
-index 7a731be..c0e1975 100644
---- a/tests/auth_tests/test_forms.py
-+++ b/tests/auth_tests/test_forms.py
-@@ -5,7 +5,7 @@ from unittest import mock
- from django.contrib.auth.forms import (
- AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm,
- PasswordResetForm, ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget,
-- SetPasswordForm, UserChangeForm, UserCreationForm,
-+ SetPasswordForm, UserChangeForm, UserCreationForm, UsernameField,
- )
- from django.contrib.auth.models import User
- from django.contrib.auth.signals import user_login_failed
-@@ -132,6 +132,12 @@ class UserCreationFormTest(TestDataMixin, TestCase):
- self.assertNotEqual(user.username, ohm_username)
- self.assertEqual(user.username, 'testΩ') # U+03A9 GREEK CAPITAL LETTER OMEGA
-
-+ def test_invalid_username_no_normalize(self):
-+ field = UsernameField(max_length=254)
-+ # Usernames are not normalized if they are too long.
-+ self.assertEqual(field.to_python("½" * 255), "½" * 255)
-+ self.assertEqual(field.to_python("ff" * 254), "ff" * 254)
-+
- def test_duplicate_normalized_unicode(self):
- """
- To prevent almost identical usernames, visually identical but differing
---
-2.30.0
-
diff --git a/CVE-2024-24680.patch b/CVE-2024-24680.patch
deleted file mode 100644
index 3d8ab4dcf517deafb344c24af51b17eba0d159cf..0000000000000000000000000000000000000000
--- a/CVE-2024-24680.patch
+++ /dev/null
@@ -1,204 +0,0 @@
-From c1171ffbd570db90ca206c30f8e2b9f691243820 Mon Sep 17 00:00:00 2001
-From: Adam Johnson
-Date: Mon, 22 Jan 2024 13:21:13 +0000
-Subject: [PATCH] [3.2.x] Fixed CVE-2024-24680 -- Mitigated potential DoS in
- intcomma template filter.
-
-Thanks Seokchan Yoon for the report.
-
-Co-authored-by: Mariusz Felisiak
-Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
-Co-authored-by: Shai Berger
----
- .../contrib/humanize/templatetags/humanize.py | 13 +-
- tests/humanize_tests/tests.py | 140 ++++++++++++++++--
- 2 files changed, 135 insertions(+), 18 deletions(-)
-
-diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
-index 753a0d9..238aaf2 100644
---- a/django/contrib/humanize/templatetags/humanize.py
-+++ b/django/contrib/humanize/templatetags/humanize.py
-@@ -70,12 +70,13 @@ def intcomma(value, use_l10n=True):
- return intcomma(value, False)
- else:
- return number_format(value, use_l10n=True, force_grouping=True)
-- orig = str(value)
-- new = re.sub(r"^(-?\d+)(\d{3})", r'\g<1>,\g<2>', orig)
-- if orig == new:
-- return new
-- else:
-- return intcomma(new, use_l10n)
-+ result = str(value)
-+ match = re.match(r"-?\d+", result)
-+ if match:
-+ prefix = match[0]
-+ prefix_with_commas = re.sub(r"\d{3}", r"\g<0>,", prefix[::-1])[::-1]
-+ result = prefix_with_commas + result[len(prefix) :]
-+ return result
-
-
- # A tuple of standard large number to their converters
-diff --git a/tests/humanize_tests/tests.py b/tests/humanize_tests/tests.py
-index a0d16bb..3c22787 100644
---- a/tests/humanize_tests/tests.py
-+++ b/tests/humanize_tests/tests.py
-@@ -66,28 +66,144 @@ class HumanizeTests(SimpleTestCase):
-
- def test_intcomma(self):
- test_list = (
-- 100, 1000, 10123, 10311, 1000000, 1234567.25, '100', '1000',
-- '10123', '10311', '1000000', '1234567.1234567',
-- Decimal('1234567.1234567'), None,
-+ 100,
-+ -100,
-+ 1000,
-+ -1000,
-+ 10123,
-+ -10123,
-+ 10311,
-+ -10311,
-+ 1000000,
-+ -1000000,
-+ 1234567.25,
-+ -1234567.25,
-+ "100",
-+ "-100",
-+ "1000",
-+ "-1000",
-+ "10123",
-+ "-10123",
-+ "10311",
-+ "-10311",
-+ "1000000",
-+ "-1000000",
-+ "1234567.1234567",
-+ "-1234567.1234567",
-+ Decimal("1234567.1234567"),
-+ Decimal("-1234567.1234567"),
-+ None,
-+ "1234567",
-+ "-1234567",
-+ "1234567.12",
-+ "-1234567.12",
-+ "the quick brown fox jumped over the lazy dog",
- )
- result_list = (
-- '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25',
-- '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567',
-- '1,234,567.1234567', None,
-+ "100",
-+ "-100",
-+ "1,000",
-+ "-1,000",
-+ "10,123",
-+ "-10,123",
-+ "10,311",
-+ "-10,311",
-+ "1,000,000",
-+ "-1,000,000",
-+ "1,234,567.25",
-+ "-1,234,567.25",
-+ "100",
-+ "-100",
-+ "1,000",
-+ "-1,000",
-+ "10,123",
-+ "-10,123",
-+ "10,311",
-+ "-10,311",
-+ "1,000,000",
-+ "-1,000,000",
-+ "1,234,567.1234567",
-+ "-1,234,567.1234567",
-+ "1,234,567.1234567",
-+ "-1,234,567.1234567",
-+ None,
-+ "1,234,567",
-+ "-1,234,567",
-+ "1,234,567.12",
-+ "-1,234,567.12",
-+ "the quick brown fox jumped over the lazy dog",
- )
- with translation.override('en'):
- self.humanize_tester(test_list, result_list, 'intcomma')
-
- def test_l10n_intcomma(self):
- test_list = (
-- 100, 1000, 10123, 10311, 1000000, 1234567.25, '100', '1000',
-- '10123', '10311', '1000000', '1234567.1234567',
-- Decimal('1234567.1234567'), None,
-+ 100,
-+ -100,
-+ 1000,
-+ -1000,
-+ 10123,
-+ -10123,
-+ 10311,
-+ -10311,
-+ 1000000,
-+ -1000000,
-+ 1234567.25,
-+ -1234567.25,
-+ "100",
-+ "-100",
-+ "1000",
-+ "-1000",
-+ "10123",
-+ "-10123",
-+ "10311",
-+ "-10311",
-+ "1000000",
-+ "-1000000",
-+ "1234567.1234567",
-+ "-1234567.1234567",
-+ Decimal("1234567.1234567"),
-+ -Decimal("1234567.1234567"),
-+ None,
-+ "1234567",
-+ "-1234567",
-+ "1234567.12",
-+ "-1234567.12",
-+ "the quick brown fox jumped over the lazy dog",
- )
- result_list = (
-- '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25',
-- '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567',
-- '1,234,567.1234567', None,
-+ "100",
-+ "-100",
-+ "1,000",
-+ "-1,000",
-+ "10,123",
-+ "-10,123",
-+ "10,311",
-+ "-10,311",
-+ "1,000,000",
-+ "-1,000,000",
-+ "1,234,567.25",
-+ "-1,234,567.25",
-+ "100",
-+ "-100",
-+ "1,000",
-+ "-1,000",
-+ "10,123",
-+ "-10,123",
-+ "10,311",
-+ "-10,311",
-+ "1,000,000",
-+ "-1,000,000",
-+ "1,234,567.1234567",
-+ "-1,234,567.1234567",
-+ "1,234,567.1234567",
-+ "-1,234,567.1234567",
-+ None,
-+ "1,234,567",
-+ "-1,234,567",
-+ "1,234,567.12",
-+ "-1,234,567.12",
-+ "the quick brown fox jumped over the lazy dog",
- )
- with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False):
- with translation.override('en'):
---
-2.33.0
-
diff --git a/CVE-2024-27351.patch b/CVE-2024-27351.patch
deleted file mode 100644
index 606f75529c7e55207151494400c465390f714941..0000000000000000000000000000000000000000
--- a/CVE-2024-27351.patch
+++ /dev/null
@@ -1,122 +0,0 @@
-From 072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521 Mon Sep 17 00:00:00 2001
-From: Shai Berger
-Date: Mon, 19 Feb 2024 13:56:37 +0100
-Subject: [PATCH] [3.2.x] Fixed CVE-2024-27351 -- Prevented potential ReDoS in
- Truncator.words().
-
-Thanks Seokchan Yoon for the report.
-
-Co-Authored-By: Mariusz Felisiak
----
- django/utils/text.py | 57 ++++++++++++++++++++++++++++++++--
- tests/utils_tests/test_text.py | 26 ++++++++++++++++
- 2 files changed, 81 insertions(+), 2 deletions(-)
-
-diff --git a/django/utils/text.py b/django/utils/text.py
-index 83e258f..88da9a2 100644
---- a/django/utils/text.py
-+++ b/django/utils/text.py
-@@ -18,8 +18,61 @@ def capfirst(x):
- return x and str(x)[0].upper() + str(x)[1:]
-
-
--# Set up regular expressions
--re_words = _lazy_re_compile(r'<[^>]+?>|([^<>\s]+)', re.S)
-+# ----- Begin security-related performance workaround -----
-+
-+# We used to have, below
-+#
-+# re_words = _lazy_re_compile(r"<[^>]+?>|([^<>\s]+)", re.S)
-+#
-+# But it was shown that this regex, in the way we use it here, has some
-+# catastrophic edge-case performance features. Namely, when it is applied to
-+# text with only open brackets "<<<...". The class below provides the services
-+# and correct answers for the use cases, but in these edge cases does it much
-+# faster.
-+re_notag = _lazy_re_compile(r"([^<>\s]+)", re.S)
-+re_prt = _lazy_re_compile(r"<|([^<>\s]+)", re.S)
-+
-+
-+class WordsRegex:
-+ @staticmethod
-+ def search(text, pos):
-+ # Look for "<" or a non-tag word.
-+ partial = re_prt.search(text, pos)
-+ if partial is None or partial[1] is not None:
-+ return partial
-+
-+ # "<" was found, look for a closing ">".
-+ end = text.find(">", partial.end(0))
-+ if end < 0:
-+ # ">" cannot be found, look for a word.
-+ return re_notag.search(text, pos + 1)
-+ else:
-+ # "<" followed by a ">" was found -- fake a match.
-+ end += 1
-+ return FakeMatch(text[partial.start(0): end], end)
-+
-+
-+class FakeMatch:
-+ __slots__ = ["_text", "_end"]
-+
-+ def end(self, group=0):
-+ assert group == 0, "This specific object takes only group=0"
-+ return self._end
-+
-+ def __getitem__(self, group):
-+ if group == 1:
-+ return None
-+ assert group == 0, "This specific object takes only group in {0,1}"
-+ return self._text
-+
-+ def __init__(self, text, end):
-+ self._text, self._end = text, end
-+
-+
-+# ----- End security-related performance workaround -----
-+
-+# Set up regular expressions.
-+re_words = WordsRegex
- re_chars = _lazy_re_compile(r'<[^>]+?>|(.)', re.S)
- re_tag = _lazy_re_compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
- re_newlines = _lazy_re_compile(r'\r\n|\r') # Used in normalize_newlines
-diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
-index 0a6f0bc..758919c 100644
---- a/tests/utils_tests/test_text.py
-+++ b/tests/utils_tests/test_text.py
-@@ -159,6 +159,32 @@ class TestUtilsText(SimpleTestCase):
- truncator = text.Truncator('I <3 python, what about you?
')
- self.assertEqual('I <3 python,…
', truncator.words(3, html=True))
-
-+ # Only open brackets.
-+ test = "<" * 60_000
-+ truncator = text.Truncator(test)
-+ self.assertEqual(truncator.words(1, html=True), test)
-+
-+ # Tags with special chars in attrs.
-+ truncator = text.Truncator(
-+ """Hello, my dear lady! """
-+ )
-+ self.assertEqual(
-+ """Hello, my dear… """,
-+ truncator.words(3, html=True),
-+ )
-+
-+ # Tags with special non-latin chars in attrs.
-+ truncator = text.Truncator("""Hello, my dear lady!
""")
-+ self.assertEqual(
-+ """Hello, my dear…
""",
-+ truncator.words(3, html=True),
-+ )
-+
-+ # Misplaced brackets.
-+ truncator = text.Truncator("hello >< world")
-+ self.assertEqual(truncator.words(1, html=True), "hello…")
-+ self.assertEqual(truncator.words(2, html=True), "hello >< world")
-+
- @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
- def test_truncate_words_html_size_limit(self):
- max_len = text.Truncator.MAX_LENGTH_HTML
---
-2.33.0
-
diff --git a/3.2.12.tar.gz b/Django-4.2.15.tar.gz
similarity index 53%
rename from 3.2.12.tar.gz
rename to Django-4.2.15.tar.gz
index 6233704040e9580429c8f4a1a77a5311ed24db39..c77fade850f7089ec82208edd58c16b145e211f3 100644
Binary files a/3.2.12.tar.gz and b/Django-4.2.15.tar.gz differ
diff --git a/backport-CVE-2022-36359.patch b/backport-CVE-2022-36359.patch
deleted file mode 100644
index 97dc3ff6ee85773ee3922c0a9caa77c368d95a43..0000000000000000000000000000000000000000
--- a/backport-CVE-2022-36359.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 8c5a1dfe34ea52cc2af21064a8654bfaa8b7a012 Mon Sep 17 00:00:00 2001
-From: Carlton Gibson
-Date: Wed, 27 Jul 2022 10:27:42 +0200
-Subject: [PATCH] [3.2.x] Fixed CVE-2022-36359: Escaped filename in
- Content-Disposition header.
-
-Thanks to Motoyasu Saburi for the report.
----
- django/http/response.py | 4 +++-
- docs/releases/3.2.15.txt | 8 ++++++-
- tests/responses/test_fileresponse.py | 35 ++++++++++++++++++++++++++++
- 3 files changed, 45 insertions(+), 2 deletions(-)
-
-diff --git a/django/http/response.py b/django/http/response.py
-index 1c22edaff3..73f87d7bda 100644
---- a/django/http/response.py
-+++ b/django/http/response.py
-@@ -485,7 +485,9 @@ class FileResponse(StreamingHttpResponse):
- disposition = 'attachment' if self.as_attachment else 'inline'
- try:
- filename.encode('ascii')
-- file_expr = 'filename="{}"'.format(filename)
-+ file_expr = 'filename="{}"'.format(
-+ filename.replace('\\', '\\\\').replace('"', r'\"')
-+ )
- except UnicodeEncodeError:
- file_expr = "filename*=utf-8''{}".format(quote(filename))
- self.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)
-diff --git a/tests/responses/test_fileresponse.py b/tests/responses/test_fileresponse.py
-index 46d407bdf5..b4ef82ef3e 100644
---- a/tests/responses/test_fileresponse.py
-+++ b/tests/responses/test_fileresponse.py
-@@ -89,3 +89,38 @@ class FileResponseTests(SimpleTestCase):
- response.headers['Content-Disposition'],
- "attachment; filename*=utf-8''%E7%A5%9D%E6%82%A8%E5%B9%B3%E5%AE%89.odt"
- )
-+
-+ def test_content_disposition_escaping(self):
-+ # fmt: off
-+ tests = [
-+ (
-+ 'multi-part-one";\" dummy".txt',
-+ r"multi-part-one\";\" dummy\".txt"
-+ ),
-+ ]
-+ # fmt: on
-+ # Non-escape sequence backslashes are path segments on Windows, and are
-+ # eliminated by an os.path.basename() check in FileResponse.
-+ if sys.platform != "win32":
-+ # fmt: off
-+ tests += [
-+ (
-+ 'multi-part-one\\";\" dummy".txt',
-+ r"multi-part-one\\\";\" dummy\".txt"
-+ ),
-+ (
-+ 'multi-part-one\\";\\\" dummy".txt',
-+ r"multi-part-one\\\";\\\" dummy\".txt"
-+ )
-+ ]
-+ # fmt: on
-+ for filename, escaped in tests:
-+ with self.subTest(filename=filename, escaped=escaped):
-+ response = FileResponse(
-+ io.BytesIO(b"binary content"), filename=filename, as_attachment=True
-+ )
-+ response.close()
-+ self.assertEqual(
-+ response.headers["Content-Disposition"],
-+ f'attachment; filename="{escaped}"',
-+ )
---
-2.36.1
-
diff --git a/python-django.spec b/python-django.spec
index 8f6c515fa2bbb3d7057e5eeec2c9e00d4433d19c..aa3ef771213e3d7701f883cc9d31508f9dba6e98 100644
--- a/python-django.spec
+++ b/python-django.spec
@@ -1,28 +1,11 @@
%global _empty_manifest_terminate_build 0
Name: python-django
-Version: 3.2.12
-Release: 10
+Version: 4.2.15
+Release: 1
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
License: Apache-2.0 and Python-2.0 and BSD-3-Clause
URL: https://www.djangoproject.com/
-Source0: https://github.com/django/django/archive/refs/tags/%{version}.tar.gz
-
-#https://github.com/django/django/commit/a9010fe5555e6086a9d9ae50069579400ef0685e
-Patch0: CVE-2022-34265.patch
-Patch1: backport-CVE-2022-36359.patch
-Patch2: CVE-2023-23969.patch
-Patch3: CVE-2023-24580.patch
-Patch4: CVE-2023-31047.patch
-Patch5: CVE-2023-36053.patch
-Patch6: CVE-2023-41164.patch
-# https://github.com/django/django/commit/ccdade1a0262537868d7ca64374de3d957ca50c5
-Patch7: CVE-2023-43665.patch
-# https://github.com/django/django/commit/f9a7fb8466a7ba4857eaf930099b5258f3eafb2b
-Patch8: CVE-2023-46695.patch
-# https://github.com/django/django/commit/c1171ffbd570db90ca206c30f8e2b9f691243820
-Patch9: CVE-2024-24680.patch
-# https://github.com/django/django/commit/072963e4c4d0b3a7a8c5412bc0c7d27d1a9c3521
-Patch10: CVE-2024-27351.patch
+Source0: https://files.pythonhosted.org/packages/source/d/Django/Django-%{version}.tar.gz
BuildArch: noarch
%description
@@ -49,7 +32,7 @@ Provides: python3-Django-doc
Development documents and examples for Django
%prep
-%autosetup -n django-%{version} -p1
+%autosetup -n Django-%{version} -p1
%build
%py3_build
@@ -89,6 +72,19 @@ mv %{buildroot}/doclist.lst .
%{_docdir}/*
%changelog
+* Thu Aug 08 2024 yaoxin - 4.2.15-1
+- Update to 4.2.15
+ * CVE-2024-41989: Memory exhaustion in ``django.utils.numberformat.floatformat()``
+ * CVE-2024-41990: Potential denial-of-service vulnerability in ``django.utils.html.urlize()``
+ * CVE-2024-41991: Potential denial-of-service vulnerability in ``django.utils.html.urlize()`` and ``AdminURLFieldWidget``
+ * CVE-2024-42005: Potential SQL injection in ``QuerySet.values()`` and ``values_list()``
+ * Fixed a regression in Django 4.2.14 that caused a crash in ``LocaleMiddleware`` when processing a language code over 500 characters
+ * CVE-2024-38875: Potential denial-of-service vulnerability in django.utils.html.urlize()
+ * CVE-2024-39329: Username enumeration through timing difference for users with unusable passwords
+ * CVE-2024-39330: Potential directory-traversal via Storage.save()
+ * CVE-2024-39614: Potential denial-of-service vulnerability in get_supported_language_variant()
+ * Fixed a crash in Django 4.2 when validating email max line lengths with content decoded using the surrogateescape error handling scheme
+
* Tue Mar 05 2024 yaoxin - 3.2.12-10
- Fix CVE-2024-27351