Skip to content

Commit 1ccb123

Browse files
authored
[3.15] gh-161102: Fix idna incremental decoder after leading empty labels (GH-161103) (#161130)
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 a8d10a0 commit 1ccb123

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
@@ -348,6 +348,9 @@ def _buffer_decode(self, input, errors, final):
348348
result = []
349349
size = 0
350350
for label in labels:
351+
if result:
352+
# Join with U+002E
353+
size += 1
351354
try:
352355
u_label = ToUnicode(label)
353356
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
@@ -360,8 +363,6 @@ def _buffer_decode(self, input, errors, final):
360363
)
361364
else:
362365
result.append(u_label)
363-
if size:
364-
size += 1
365366
size += len(label)
366367

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

‎Lib/test/test_codecs.py‎

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

1689+
def test_incremental_decode_leading_empty_labels(self):
1690+
# The dot after an empty label at the start of the input has to be
1691+
# counted as consumed, or it is decoded again by the next call.
1692+
for data in b".python.org", b"..python.org.", b"..", b"." * 100:
1693+
with self.subTest(data=data):
1694+
decoder = codecs.getincrementaldecoder("idna")()
1695+
result = decoder.decode(data)
1696+
self.assertNotIn(b".", decoder.getstate()[0])
1697+
result += decoder.decode(b"", True)
1698+
self.assertEqual(result, data.decode("idna"))
1699+
self.assertEqual(decoder.getstate(), (b"", 0))
1700+
self.assertEqual(decoder.decode(b"", True), "")
1701+
1702+
with io.TextIOWrapper(io.BytesIO(b"." * 100), encoding="idna") as f:
1703+
self.assertEqual(f.readline(), "." * 100)
1704+
16871705
def test_incremental_decode_invalid(self):
16881706
iterdecode_testcases = [
16891707
(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)