+113
-11
lines changedFilter options
+113
-11
lines changed Original file line number Diff line number Diff line change
@@ -67,8 +67,14 @@ def _generator():
67
67
class Truncator(SimpleLazyObject):
68
68
"""
69
69
An object used to truncate text, either by characters or words.
70
+
71
+
When truncating HTML text (either chars or words), input will be limited to
72
+
at most `MAX_LENGTH_HTML` characters.
70
73
"""
71
74
75
+
# 5 million characters are approximately 4000 text pages or 3 web pages.
76
+
MAX_LENGTH_HTML = 5_000_000
77
+
72
78
def __init__(self, text):
73
79
super().__init__(lambda: str(text))
74
80
@@ -164,6 +170,11 @@ def _truncate_html(self, length, truncate, text, truncate_len, words):
164
170
if words and length <= 0:
165
171
return ""
166
172
173
+
size_limited = False
174
+
if len(text) > self.MAX_LENGTH_HTML:
175
+
text = text[: self.MAX_LENGTH_HTML]
176
+
size_limited = True
177
+
167
178
html4_singlets = (
168
179
"br",
169
180
"col",
@@ -220,10 +231,14 @@ def _truncate_html(self, length, truncate, text, truncate_len, words):
220
231
# Add it to the start of the open tags list
221
232
open_tags.insert(0, tagname)
222
233
234
+
truncate_text = self.add_truncation_text("", truncate)
235
+
223
236
if current_len <= length:
237
+
if size_limited and truncate_text:
238
+
text += truncate_text
224
239
return text
240
+
225
241
out = text[:end_text_pos]
226
-
truncate_text = self.add_truncation_text("", truncate)
227
242
if truncate_text:
228
243
out += truncate_text
229
244
# Close any tags still open
Original file line number Diff line number Diff line change
@@ -2652,6 +2652,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
2652
2652
2653
2653
Newlines in the HTML content will be preserved.
2654
2654
2655
+
.. admonition:: Size of input string
2656
+
2657
+
Processing large, potentially malformed HTML strings can be
2658
+
resource-intensive and impact service performance. ``truncatechars_html``
2659
+
limits input to the first five million characters.
2660
+
2661
+
.. versionchanged:: 3.2.22
2662
+
2663
+
In older versions, strings over five million characters were processed.
2664
+
2655
2665
.. templatefilter:: truncatewords
2656
2666
2657
2667
``truncatewords``
@@ -2694,6 +2704,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
2694
2704
2695
2705
Newlines in the HTML content will be preserved.
2696
2706
2707
+
.. admonition:: Size of input string
2708
+
2709
+
Processing large, potentially malformed HTML strings can be
2710
+
resource-intensive and impact service performance. ``truncatewords_html``
2711
+
limits input to the first five million characters.
2712
+
2713
+
.. versionchanged:: 3.2.22
2714
+
2715
+
In older versions, strings over five million characters were processed.
2716
+
2697
2717
.. templatefilter:: unordered_list
2698
2718
2699
2719
``unordered_list``
Original file line number Diff line number Diff line change
@@ -6,4 +6,20 @@ Django 3.2.22 release notes
6
6
7
7
Django 3.2.22 fixes a security issue with severity "moderate" in 3.2.21.
8
8
9
-
...
9
+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
10
+
================================================================================
11
+
12
+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
13
+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
14
+
methods (with ``html=True``) were revised and improved. However, these regular
15
+
expressions still exhibited linear backtracking complexity, so when given a
16
+
very long, potentially malformed HTML input, the evaluation would still be
17
+
slow, leading to a potential denial of service vulnerability.
18
+
19
+
The ``chars()`` and ``words()`` methods are used to implement the
20
+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
21
+
filters, which were thus also vulnerable.
22
+
23
+
The input processed by ``Truncator``, when operating in HTML mode, has been
24
+
limited to the first five million characters in order to avoid potential
25
+
performance and memory issues.
Original file line number Diff line number Diff line change
@@ -6,4 +6,20 @@ Django 4.1.12 release notes
6
6
7
7
Django 4.1.12 fixes a security issue with severity "moderate" in 4.1.11.
8
8
9
-
...
9
+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
10
+
================================================================================
11
+
12
+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
13
+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
14
+
methods (with ``html=True``) were revised and improved. However, these regular
15
+
expressions still exhibited linear backtracking complexity, so when given a
16
+
very long, potentially malformed HTML input, the evaluation would still be
17
+
slow, leading to a potential denial of service vulnerability.
18
+
19
+
The ``chars()`` and ``words()`` methods are used to implement the
20
+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
21
+
filters, which were thus also vulnerable.
22
+
23
+
The input processed by ``Truncator``, when operating in HTML mode, has been
24
+
limited to the first five million characters in order to avoid potential
25
+
performance and memory issues.
Original file line number Diff line number Diff line change
@@ -7,6 +7,24 @@ Django 4.2.6 release notes
7
7
Django 4.2.6 fixes a security issue with severity "moderate" and several bugs
8
8
in 4.2.5.
9
9
10
+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
11
+
================================================================================
12
+
13
+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
14
+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
15
+
methods (with ``html=True``) were revised and improved. However, these regular
16
+
expressions still exhibited linear backtracking complexity, so when given a
17
+
very long, potentially malformed HTML input, the evaluation would still be
18
+
slow, leading to a potential denial of service vulnerability.
19
+
20
+
The ``chars()`` and ``words()`` methods are used to implement the
21
+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
22
+
filters, which were thus also vulnerable.
23
+
24
+
The input processed by ``Truncator``, when operating in HTML mode, has been
25
+
limited to the first five million characters in order to avoid potential
26
+
performance and memory issues.
27
+
10
28
Bugfixes
11
29
========
12
30
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
1
1
import json
2
2
import sys
3
+
from unittest.mock import patch
3
4
4
5
from django.core.exceptions import SuspiciousFileOperation
5
6
from django.test import SimpleTestCase
@@ -94,11 +95,17 @@ def test_truncate_chars(self):
94
95
text.Truncator(lazystr("The quick brown fox")).chars(10), "The quick…"
95
96
)
96
97
97
-
def test_truncate_chars_html(self):
98
+
@patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
99
+
def test_truncate_chars_html_size_limit(self):
100
+
max_len = text.Truncator.MAX_LENGTH_HTML
101
+
bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
102
+
valid_html = "<p>Joel is a slug</p>" # 14 chars
98
103
perf_test_values = [
99
-
(("</a" + "\t" * 50000) + "//>", None),
100
-
("&" * 50000, "&" * 9 + "…"),
104
+
("</a" + "\t" * (max_len - 6) + "//>", None),
105
+
("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * 6 + "…"),
106
+
("&" * bigger_len, "&" * 9 + "…"),
101
107
("_X<<<<<<<<<<<>", None),
108
+
(valid_html * bigger_len, "<p>Joel is a…</p>"), # 10 chars
102
109
]
103
110
for value, expected in perf_test_values:
104
111
with self.subTest(value=value):
@@ -176,15 +183,25 @@ def test_truncate_html_words(self):
176
183
truncator = text.Truncator("<p>I <3 python, what about you?</p>")
177
184
self.assertEqual("<p>I <3 python,…</p>", truncator.words(3, html=True))
178
185
186
+
@patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
187
+
def test_truncate_words_html_size_limit(self):
188
+
max_len = text.Truncator.MAX_LENGTH_HTML
189
+
bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
190
+
valid_html = "<p>Joel is a slug</p>" # 4 words
179
191
perf_test_values = [
180
-
("</a" + "\t" * 50000) + "//>",
181
-
"&" * 50000,
182
-
"_X<<<<<<<<<<<>",
192
+
("</a" + "\t" * (max_len - 6) + "//>", None),
193
+
("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * (max_len - 3) + "…"),
194
+
("&" * max_len, None), # no change
195
+
("&" * bigger_len, "&" * max_len + "…"),
196
+
("_X<<<<<<<<<<<>", None),
197
+
(valid_html * bigger_len, valid_html * 12 + "<p>Joel is…</p>"), # 50 words
183
198
]
184
-
for value in perf_test_values:
199
+
for value, expected in perf_test_values:
185
200
with self.subTest(value=value):
186
201
truncator = text.Truncator(value)
187
-
self.assertEqual(value, truncator.words(50, html=True))
202
+
self.assertEqual(
203
+
expected if expected else value, truncator.words(50, html=True)
204
+
)
188
205
189
206
def test_wrap(self):
190
207
digits = "1234 67 9"
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4