Skip to content

Commit f839f5c

Browse files
authored
[3.14] gh-161102: Fix idna incremental decoder after leading empty labels (GH-161103) (#161129)
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 (cherry picked from commit 2982d93)
1 parent 583d7b6 commit f839f5c

3 files changed

Lines changed: 25 additions & 2 deletions

File tree

‎Lib/encodings/idna.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ def _buffer_decode(self, input, errors, final):
347347
result = []
348348
size = 0
349349
for label in labels:
350+
if result:
351+
# Join with U+002E
352+
size += 1
350353
try:
351354
u_label = ToUnicode(label)
352355
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
@@ -359,8 +362,6 @@ def _buffer_decode(self, input, errors, final):
359362
)
360363
else:
361364
result.append(u_label)
362-
if size:
363-
size += 1
364365
size += len(label)
365366

366367
result = ".".join(result) + trailing_dot

‎Lib/test/test_codecs.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,8 @@ class IDNACodecTest(unittest.TestCase):
15861586
(b"xn--pythn-&mua.org", UnicodeDecodeError("idna", b"xn--pythn-&mua.org", 10, 11, "")),
15871587
(b"xn--pythn-m&ua.org", UnicodeDecodeError("idna", b"xn--pythn-m&ua.org", 11, 12, "")),
15881588
(b"xn--pythn-mua&.org", UnicodeDecodeError("idna", b"xn--pythn-mua&.org", 13, 14, "")),
1589+
(b"bugs.xn--pythn-&mua.org", UnicodeDecodeError("idna", b"bugs.xn--pythn-&mua.org", 15, 16, "")),
1590+
(b".xn--pythn-&mua.org", UnicodeDecodeError("idna", b".xn--pythn-&mua.org", 11, 12, "")),
15891591
]
15901592
invalid_encode_testcases = [
15911593
(f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"foo.{'\xff'*60}", 4, 64, "")),
@@ -1683,6 +1685,22 @@ def test_incremental_decode(self):
16831685
self.assertEqual(decoder.decode(b"rg."), "org.")
16841686
self.assertEqual(decoder.decode(b"", True), "")
16851687

1688+
def test_incremental_decode_leading_empty_labels(self):
1689+
# The dot after an empty label at the start of the input has to be
1690+
# counted as consumed, or it is decoded again by the next call.
1691+
for data in b".python.org", b"..python.org.", b"..", b"." * 100:
1692+
with self.subTest(data=data):
1693+
decoder = codecs.getincrementaldecoder("idna")()
1694+
result = decoder.decode(data)
1695+
self.assertNotIn(b".", decoder.getstate()[0])
1696+
result += decoder.decode(b"", True)
1697+
self.assertEqual(result, data.decode("idna"))
1698+
self.assertEqual(decoder.getstate(), (b"", 0))
1699+
self.assertEqual(decoder.decode(b"", True), "")
1700+
1701+
with io.TextIOWrapper(io.BytesIO(b"." * 100), encoding="idna") as f:
1702+
self.assertEqual(f.readline(), "." * 100)
1703+
16861704
def test_incremental_decode_invalid(self):
16871705
iterdecode_testcases = [
16881706
(b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix the incremental decoder of the :mod:`idna <encodings.idna>` codec
2+
decoding some dots twice when its input starts with empty labels, for example
3+
``b".example.com"``. Reading a run of dots through :class:`io.TextIOWrapper`
4+
no longer produces quadratically more text than the input holds.

0 commit comments

Comments
 (0)