https://github.com/python/cpython/commit/2982d9388cef180166e040fe0d4e59be9c824a87
commit: 2982d9388cef180166e040fe0d4e59be9c824a87
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T18:45:58Z
summary:
gh-161102: Fix idna incremental decoder after leading empty labels (GH-161103)
Count the separating dot for every label after the first, so dots after
leading empty labels are not decoded again by the next call.
ANT-2026-KFX1PUWZ
files:
A Misc/NEWS.d/next/Library/2026-10-22-05-20-02.gh-issue-161102.gqDOIM7X.rst
M Lib/encodings/idna.py
M Lib/test/test_codecs.py
diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py
index 98d7d891594c64..d5c06c7c73f027 100644
--- a/Lib/encodings/idna.py
+++ b/Lib/encodings/idna.py
@@ -364,6 +364,9 @@ def _buffer_decode(self, input, errors, final):
result = []
size = 0
for label in labels:
+ if result:
+ # Join with U+002E
+ size += 1
try:
u_label = ToUnicode(label)
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
@@ -376,8 +379,6 @@ def _buffer_decode(self, input, errors, final):
)
else:
result.append(u_label)
- if size:
- size += 1
size += len(label)
result = ".".join(result) + trailing_dot
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index f26a65bb67acb1..4492e4d874189f 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -1671,6 +1671,8 @@ class IDNACodecTest(unittest.TestCase):
(b"xn--pythn-&mua.org", UnicodeDecodeError("idna",
b"xn--pythn-&mua.org", 10, 11, "")),
(b"xn--pythn-m&ua.org", UnicodeDecodeError("idna",
b"xn--pythn-m&ua.org", 11, 12, "")),
(b"xn--pythn-mua&.org", UnicodeDecodeError("idna",
b"xn--pythn-mua&.org", 13, 14, "")),
+ (b"bugs.xn--pythn-&mua.org", UnicodeDecodeError("idna",
b"bugs.xn--pythn-&mua.org", 15, 16, "")),
+ (b".xn--pythn-&mua.org", UnicodeDecodeError("idna",
b".xn--pythn-&mua.org", 11, 12, "")),
]
invalid_encode_testcases = [
(f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"foo.{'\xff'*60}", 4,
64, "")),
@@ -1790,6 +1792,22 @@ def test_incremental_decode(self):
self.assertEqual(decoder.decode(b"rg."), "org.")
self.assertEqual(decoder.decode(b"", True), "")
+ def test_incremental_decode_leading_empty_labels(self):
+ # The dot after an empty label at the start of the input has to be
+ # counted as consumed, or it is decoded again by the next call.
+ for data in b".python.org", b"..python.org.", b"..", b"." * 100:
+ with self.subTest(data=data):
+ decoder = codecs.getincrementaldecoder("idna")()
+ result = decoder.decode(data)
+ self.assertNotIn(b".", decoder.getstate()[0])
+ result += decoder.decode(b"", True)
+ self.assertEqual(result, data.decode("idna"))
+ self.assertEqual(decoder.getstate(), (b"", 0))
+ self.assertEqual(decoder.decode(b"", True), "")
+
+ with io.TextIOWrapper(io.BytesIO(b"." * 100), encoding="idna") as f:
+ self.assertEqual(f.readline(), "." * 100)
+
def test_incremental_decode_invalid(self):
iterdecode_testcases = [
(b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")),
diff --git
a/Misc/NEWS.d/next/Library/2026-10-22-05-20-02.gh-issue-161102.gqDOIM7X.rst
b/Misc/NEWS.d/next/Library/2026-10-22-05-20-02.gh-issue-161102.gqDOIM7X.rst
new file mode 100644
index 00000000000000..e3089a0560bcbf
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-05-20-02.gh-issue-161102.gqDOIM7X.rst
@@ -0,0 +1,4 @@
+Fix the incremental decoder of the :mod:`idna <encodings.idna>` codec
+decoding some dots twice when its input starts with empty labels, for example
+``b".example.com"``. Reading a run of dots through :class:`io.TextIOWrapper`
+no longer produces quadratically more text than the input holds.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]