Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
bb91678
gh-152798: Return 'pymutex' on POSIX except Windows/WASI
Wojusensei Jul 4, 2026
b6160b4
gh-152851: fix a crash when copying a BLAKE-2s/2b object (#153008)
picnixz Jul 4, 2026
b8ade57
gh-153009: Fix compilation of `curses` on platforms that define the `…
picnixz Jul 4, 2026
61f111e
gh-135661: Fix abrupt closing of empty comment in HTMLParser (GH-153007)
serhiy-storchaka Jul 4, 2026
844dbd6
gh-152068: Reset PyREPL Colors on prompt finish (#152108)
edvilme Jul 4, 2026
612929b
gh-150459: Fix `SyntaxError` message for `from x lazy import y` (#150…
sobolevn Jul 4, 2026
f806c77
gh-151945: Fix all reference warnings and various solecisms in the `h…
harjothkhara Jul 4, 2026
00a98ea
gh-153030: Fix quadratic complexity in incremental parsing in HTMLPar…
serhiy-storchaka Jul 4, 2026
6355129
gh-151378: Fix unbounded memory growth when recording binary and the …
maurycy Jul 4, 2026
8063bb1
gh-152718: Reject oversized table counts in the profiling binary read…
tonghuaroot Jul 4, 2026
644ba17
Add "new in 3.16" to idlelib line (GH-152788) (#153049)
terryjreedy Jul 4, 2026
45b73e0
Add "new in 3.15" comments to idlelib (#153052)
terryjreedy Jul 5, 2026
1bea11a
Remove PDF images (#145977)
StanFromIreland Jul 5, 2026
338520c
gh-40038: Quote imaplib command arguments when necessary (GH-152703)
serhiy-storchaka Jul 5, 2026
01ab25b
gh-151819: Clarify the conditional-pattern email example in re docs (…
serhiy-storchaka Jul 5, 2026
ca35692
gh-66335: Test uppercase IMAP4 command names (GH-152876)
serhiy-storchaka Jul 5, 2026
d161d95
gh-151945: fix Sphinx reference warnings in `http.server` docs (#153084)
picnixz Jul 5, 2026
bec650e
gh-54930: Send a status line in error responses to malformed request …
serhiy-storchaka Jul 5, 2026
12cccf2
gh-69134: Wait until mapped in SimpleDialog keyboard tests (GH-152690)
serhiy-storchaka Jul 5, 2026
23a09b2
Fix various typos in the `http.server` docs (#153089)
picnixz Jul 5, 2026
0711383
gh-123193: Document tkinter Variable lifetime (GH-152625)
serhiy-storchaka Jul 5, 2026
d87decf
gh-75952: Document negative offsets in tkinter geometry strings (GH-1…
serhiy-storchaka Jul 5, 2026
4fcfa1f
gh-79638: Restore "Treat an unreachable robots.txt as disallow all" (…
serhiy-storchaka Jul 5, 2026
c4f7913
gh-76607: Improve documentation of the tkinter cursor option (GH-152481)
serhiy-storchaka Jul 5, 2026
93657e1
Fix minor typos in 'Compound statements' docs (#149666)
jonathandung Jul 5, 2026
cb10926
gh-59396: Use themed widgets in tkinter.filedialog (GH-152036)
serhiy-storchaka Jul 5, 2026
51f1772
gh-43699: Defer the drag cursor in tkinter.dnd until the pointer move…
serhiy-storchaka Jul 5, 2026
7d8d1a4
gh-149930: Clarify imaplib response helper types (GH-149963)
savagemechanic Jul 5, 2026
06e9057
gh-68147: Fix RFC references in imaplib (GH-153126)
serhiy-storchaka Jul 5, 2026
5cd5870
gh-143921: Narrow the control character check in imaplib commands (GH…
serhiy-storchaka Jul 5, 2026
fd7d48d
gh-60055: Allow passing a Request instance for RobotParser URLs (#103…
ephphatha Jul 5, 2026
d6b9cb3
gh-77508: Add imaplib.IMAP4.move method (GH-153121)
serhiy-storchaka Jul 5, 2026
b5326b6
gh-105708: 'V' could be case insensitive for IPvFuture hostnames (#10…
csreddy98 Jul 5, 2026
78b6db6
gh-98092: Add imaplib.IMAP4.id method (GH-153136)
serhiy-storchaka Jul 5, 2026
35ea24c
gh-153141: Fix mutable default argument in _SharedMemoryTracker.__ini…
whyvineet Jul 5, 2026
d3ff1e8
bpo-45706: Add imaplib.IMAP4.login_plain (GH-29398)
przemub Jul 5, 2026
0ae768b
gh-151640: Track sharing of `BytesIO` buffer in free-threaded builds …
pedramkarimii Jul 6, 2026
c78ea51
gh-82151: Update and improve urlsplit docstring (#152985)
pranavchoudhary-tech Jul 6, 2026
9fda8e7
gh-152026: Track mark-saving contexts with a counter (GH-153160)
serhiy-storchaka Jul 6, 2026
410d102
gh-148765: Fix description of *include_hidden* parameter in `glob.glo…
EoinTrial Jul 6, 2026
e9ae92d
gh-145855: Remove the DELETE_ATTR bytecode (GH-146216)
aisk Jul 6, 2026
f48a1fe
gh-153171: Produce specialized syntax errors for 'not' after an opera…
serhiy-storchaka Jul 6, 2026
bca7b6b
Add tests for syntax error messages that had no test coverage (GH-153…
serhiy-storchaka Jul 6, 2026
24d6f09
gh-145098: Use `macos-26[-intel]` instead of `macos-15[-intel]` in `{…
hugovk Jul 6, 2026
dcd75ba
gh-148286: Remove invalid test in `test_decodeescape` (#153187)
StanFromIreland Jul 6, 2026
573bcfd
gh-152849: Fix `OverflowError` message for out-of-range timestamps in…
tonghuaroot Jul 6, 2026
2a7a649
Docs: Link to the `contextlib.chdir` context manager in the `os.chdir…
cmaloney Jul 6, 2026
0048a8e
gh-152785: Upgrade Ubuntu from 24.04 to 26.04 in GitHub Actions (#152…
vstinner Jul 6, 2026
aadb053
gh-152433: Allow _ssl module to build for Windows UWP API set (GH-152…
thexai Jul 6, 2026
f804cdd
gh-150880: Clarify DirEntry.path construction semantics (GH-153218)
zainnadeem786 Jul 6, 2026
3607cc9
gh-153200: Fix math.isqrt() for int subclasses with overridden compar…
serhiy-storchaka Jul 6, 2026
a35ddfa
Seed pip caches for docs on `main`/`3.x` branches (#153127)
hugovk Jul 6, 2026
0ff8c96
gh-150490: Raise PyType_Modified for insertion into split dictionary …
DinoV Jul 6, 2026
6efa411
gh-150479: reject CR and LF in email.utils.formataddr (#150480)
metsw24-max Jul 6, 2026
7eb60bd
gh-153068: Preserve cprofile enable errors (#153070)
aisk Jul 6, 2026
eba35f9
gh-140729: Fix the cProfile module when the executed script contains …
YvesDup Jul 6, 2026
f77a7d5
gh-145177: Update Emscripten to 6.0.2 (#153235)
hoodmane Jul 6, 2026
ad42bd6
gh-153210: Fix `array` module import crash under a memory pressure (#…
sobolevn Jul 7, 2026
9964dc3
gh-153227: Use a development version of Pygments (#153228)
ZeroIntensity Jul 7, 2026
e2de1a2
gh-143990: Allow tkinter.font.Font to wrap a font description (GH-152…
serhiy-storchaka Jul 7, 2026
34e6a4e
gh-152798: Update sys.thread_info.lock documentation 2 match implemen…
Wojusensei Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
gh-153030: Fix quadratic complexity in incremental parsing in HTMLPar…
…ser (GH-153031)

When an unterminated construct (e.g. a tag or comment) spanned many
feed() calls, rescanning the growing buffer and concatenating new data
onto it were both quadratic.  New data is now accumulated in a list and
only joined and parsed once enough has piled up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
  • Loading branch information
2 people authored and Wojusensei committed Jul 7, 2026
commit 00a98ea4589bf017f1e2e338ad1cd2fc2b8105e9
32 changes: 30 additions & 2 deletions Lib/html/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ def reset(self):
self.cdata_elem = None
self._support_cdata = True
self._escapable = True
self._pending = []
self._pending_len = 0
self._parse_threshold = 1
super().reset()

def feed(self, data):
Expand All @@ -165,11 +168,36 @@ def feed(self, data):
Call this as often as you want, with as little or as much text
as you want (may include '\n').
"""
self.rawdata = self.rawdata + data
self.goahead(0)
# Accumulate new data in a list and only join and parse it once
# enough has piled up. Rescanning an unparsed buffer (e.g. an
# unterminated tag) and concatenating onto it on every call would
# both be quadratic in the input size.
self._pending_len += len(data)
if self._pending_len < self._parse_threshold:
self._pending.append(data)
else:
if not self._pending:
self.rawdata += data
else:
self._pending.append(data)
self.rawdata += ''.join(self._pending)
self._pending.clear()
self._pending_len = 0
n = len(self.rawdata)
self.goahead(0)
if len(self.rawdata) < n:
# Some data was parsed; resume on the next call.
self._parse_threshold = 1
else:
# Nothing was parsed; wait until the buffer doubles.
self._parse_threshold = len(self.rawdata)

def close(self):
"""Handle any buffered data."""
if self._pending:
self.rawdata += ''.join(self._pending)
self._pending.clear()
self._pending_len = 0
self.goahead(1)

__starttag_text = None
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_htmlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,26 @@ def check(source):
check("<![CDATA[" * 9 * n)
check("<!doctype" * 35 * n)

@support.requires_resource('cpu')
def test_incremental_no_quadratic_complexity(self):
# An unterminated construct fed in many small chunks used to take
# quadratic time, both to rescan and to concatenate the buffer.
# Now it takes a fraction of a second.
def check(prefix, chunk, suffix):
parser = html.parser.HTMLParser()
parser.feed(prefix)
for _ in range(200_000):
parser.feed(chunk)
parser.feed(suffix)
parser.close()
chunk = "a" * 64
check("<!--", chunk, "-->") # comment
check("<?", chunk, ">") # processing instruction
check("<!doctype ", chunk, ">") # doctype
check("<![CDATA[", chunk, "]]>") # CDATA section
check("<a href='", chunk, "'>") # start tag
check("<script>", chunk, "</script>") # RAWTEXT element


class AttributesTestCase(TestCaseBase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed quadratic complexity in incremental parsing of long unterminated
constructs (such as tags or comments) in :class:`html.parser.HTMLParser`,
which could be exploited for a denial of service.