From 89660cdf65216843a8c56b39fb52cb21ae5d9f27 Mon Sep 17 00:00:00 2001 From: Cnashn Date: Sat, 25 Jul 2026 17:54:19 +0300 Subject: [PATCH] gh-47482: Reject non-bytes-like input in the idna codec decoder The decoder called bytes(input) on any non-bytes input, a bogus Python 3 forward-port. Objects that bytes() accepts were silently coerced instead of rejected: codecs.decode(5, "idna") returned five NUL characters and codecs.decode([65, 66], "idna") returned "AB". Raise TypeError with the same message other codecs use, while continuing to accept bytes-like objects such as bytearray and memoryview. --- Lib/encodings/idna.py | 12 ++++++----- Lib/test/test_codecs.py | 20 +++++++++++++++++++ ...6-07-25-15-12-33.gh-issue-47482.Kq7Vn2.rst | 5 +++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-25-15-12-33.gh-issue-47482.Kq7Vn2.rst diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py index c896ffdeadfef7e..b291a45b4c059c2 100644 --- a/Lib/encodings/idna.py +++ b/Lib/encodings/idna.py @@ -234,14 +234,16 @@ def decode(self, input, errors='strict'): if errors != 'strict': raise UnicodeError(f"Unsupported error handling: {errors}") + if not isinstance(input, bytes): + try: + input = bytes(memoryview(input)) + except TypeError: + raise TypeError("a bytes-like object is required, not " + f"'{type(input).__name__}'") from None + if not input: return "", 0 - # IDNA allows decoding to operate on Unicode strings, too. - if not isinstance(input, bytes): - # XXX obviously wrong, see #3232 - input = bytes(input) - if ace_prefix not in input.lower(): # Fast path try: diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 31704955df3e14e..89e60909efe71e7 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1688,6 +1688,26 @@ def test_builtin_decode_invalid(self): self.assertEqual(exc.start, expected.start, msg=f'reason: {exc.reason}') self.assertEqual(exc.end, expected.end) + def test_decode_bytes_like(self): + # Bytes-like objects other than bytes are still accepted. + for case in (bytearray(b"xn--pythn-mua.org"), + memoryview(b"xn--pythn-mua.org")): + with self.subTest(case=case): + self.assertEqual(codecs.decode(case, "idna"), "pyth\xf6n.org") + + def test_decode_invalid_type(self): + # The decoder used to call bytes(input) on any non-bytes input, which + # silently produced nonsense for objects bytes() happens to accept: + # decoding 5 returned '\x00\x00\x00\x00\x00' and [65, 66] returned 'AB'. + for case in ("python.org", "", 5, 0, [65, 66], (67,), None, 3.5, {}): + with self.subTest(case=case): + with self.assertRaises(TypeError) as cm: + codecs.decode(case, "idna") + self.assertEqual( + str(cm.exception), + "a bytes-like object is required, " + f"not '{type(case).__name__}'") + def test_builtin_encode(self): self.assertEqual("python.org".encode("idna"), b"python.org") self.assertEqual("python.org.".encode("idna"), b"python.org.") diff --git a/Misc/NEWS.d/next/Library/2026-07-25-15-12-33.gh-issue-47482.Kq7Vn2.rst b/Misc/NEWS.d/next/Library/2026-07-25-15-12-33.gh-issue-47482.Kq7Vn2.rst new file mode 100644 index 000000000000000..8aa194062d26c8d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-25-15-12-33.gh-issue-47482.Kq7Vn2.rst @@ -0,0 +1,5 @@ +The ``idna`` codec now raises :exc:`TypeError` when asked to decode an object +that is not bytes-like, instead of silently coercing it with ``bytes()``. +Previously ``codecs.decode(5, "idna")`` returned ``'\x00\x00\x00\x00\x00'`` +and ``codecs.decode([65, 66], "idna")`` returned ``'AB'``. Bytes-like objects +such as :class:`bytearray` and :class:`memoryview` are still accepted.