Skip to content

Commit 514e77b

Browse files
committed
gh-161102: Fix idna incremental decoder after leading empty labels
Count the separating dot for every label after the first, so dots after leading empty labels are not decoded again by the next call.
1 parent 9c0cfb1 commit 514e77b

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
@@ -364,6 +364,9 @@ def _buffer_decode(self, input, errors, final):
364364
result = []
365365
size = 0
366366
for label in labels:
367+
if result:
368+
# Join with U+002E
369+
size += 1
367370
try:
368371
u_label = ToUnicode(label)
369372
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
@@ -376,8 +379,6 @@ def _buffer_decode(self, input, errors, final):
376379
)
377380
else:
378381
result.append(u_label)
379-
if size:
380-
size += 1
381382
size += len(label)
382383

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

‎Lib/test/test_codecs.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,8 @@ class IDNACodecTest(unittest.TestCase):
16671667
(b"xn--pythn-&mua.org", UnicodeDecodeError("idna", b"xn--pythn-&mua.org", 10, 11, "")),
16681668
(b"xn--pythn-m&ua.org", UnicodeDecodeError("idna", b"xn--pythn-m&ua.org", 11, 12, "")),
16691669
(b"xn--pythn-mua&.org", UnicodeDecodeError("idna", b"xn--pythn-mua&.org", 13, 14, "")),
1670+
(b"bugs.xn--pythn-&mua.org", UnicodeDecodeError("idna", b"bugs.xn--pythn-&mua.org", 15, 16, "")),
1671+
(b".xn--pythn-&mua.org", UnicodeDecodeError("idna", b".xn--pythn-&mua.org", 11, 12, "")),
16701672
]
16711673
invalid_encode_testcases = [
16721674
(f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"foo.{'\xff'*60}", 4, 64, "")),
@@ -1786,6 +1788,22 @@ def test_incremental_decode(self):
17861788
self.assertEqual(decoder.decode(b"rg."), "org.")
17871789
self.assertEqual(decoder.decode(b"", True), "")
17881790

1791+
def test_incremental_decode_leading_empty_labels(self):
1792+
# The dot after an empty label at the start of the input has to be
1793+
# counted as consumed, or it is decoded again by the next call.
1794+
for data in b".python.org", b"..python.org.", b"..", b"." * 100:
1795+
with self.subTest(data=data):
1796+
decoder = codecs.getincrementaldecoder("idna")()
1797+
result = decoder.decode(data)
1798+
self.assertNotIn(b".", decoder.getstate()[0])
1799+
result += decoder.decode(b"", True)
1800+
self.assertEqual(result, data.decode("idna"))
1801+
self.assertEqual(decoder.getstate(), (b"", 0))
1802+
self.assertEqual(decoder.decode(b"", True), "")
1803+
1804+
with io.TextIOWrapper(io.BytesIO(b"." * 100), encoding="idna") as f:
1805+
self.assertEqual(f.readline(), "." * 100)
1806+
17891807
def test_incremental_decode_invalid(self):
17901808
iterdecode_testcases = [
17911809
(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)