From d77a4c5ab43a2ea313b6a229fa754154d6532ca7 Mon Sep 17 00:00:00 2001 From: Pradyumn jha Date: Sat, 10 Oct 2026 00:38:08 +0530 Subject: [PATCH] gh-159071: Correct UnicodeDecodeError offsets in utf-8-sig codec when input starts with BOM Signed-off-by: Pradyumn jha --- Lib/encodings/utf_8_sig.py | 16 +++--- Lib/test/test_codecs.py | 53 +++++++++++++++++++ ...-10-10-00-37-12.gh-issue-159071.a62BQP.rst | 2 + 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-10-00-37-12.gh-issue-159071.a62BQP.rst diff --git a/Lib/encodings/utf_8_sig.py b/Lib/encodings/utf_8_sig.py index cc895cddcc561bc..b94caf860819fc3 100644 --- a/Lib/encodings/utf_8_sig.py +++ b/Lib/encodings/utf_8_sig.py @@ -16,12 +16,10 @@ def encode(input, errors='strict'): len(input)) def decode(input, errors='strict'): - prefix = 0 if input[:3] == codecs.BOM_UTF8: - input = input[3:] - prefix = 3 - (output, consumed) = codecs.utf_8_decode(input, errors, True) - return (output, consumed+prefix) + (output, consumed) = codecs.utf_8_decode(input, errors, True) + return (output[1:], consumed) + return codecs.utf_8_decode(input, errors, True) class IncrementalEncoder(codecs.IncrementalEncoder): def __init__(self, errors='strict'): @@ -64,8 +62,8 @@ def _buffer_decode(self, input, errors, final): self.first = 0 if input[:3] == codecs.BOM_UTF8: (output, consumed) = \ - codecs.utf_8_decode(input[3:], errors, final) - return (output, consumed+3) + codecs.utf_8_decode(input, errors, final) + return (output[1:], consumed) return codecs.utf_8_decode(input, errors, final) def reset(self): @@ -110,8 +108,8 @@ def decode(self, input, errors='strict'): return ("", 0) elif input[:3] == codecs.BOM_UTF8: self.decode = codecs.utf_8_decode - (output, consumed) = codecs.utf_8_decode(input[3:],errors) - return (output, consumed+3) + (output, consumed) = codecs.utf_8_decode(input, errors) + return (output[1:], consumed) # (else) no BOM present self.decode = codecs.utf_8_decode return codecs.utf_8_decode(input, errors) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 37ac57063c35390..9b5f475e715ec9c 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1169,6 +1169,59 @@ def test_stream_bare(self): got = ostream.getvalue() self.assertEqual(got, unistring) + def test_decode_error_offsets_with_bom(self): + # gh-159071: UnicodeDecodeError offsets should count the BOM + data = codecs.BOM_UTF8 + b"abc\xff" + with self.assertRaises(UnicodeDecodeError) as cm: + data.decode("utf-8-sig") + exc = cm.exception + self.assertEqual(exc.object, data) + self.assertEqual(exc.start, 6) + self.assertEqual(exc.end, 7) + + # Test error right after BOM + data = codecs.BOM_UTF8 + b"\xff" + with self.assertRaises(UnicodeDecodeError) as cm: + data.decode("utf-8-sig") + exc = cm.exception + self.assertEqual(exc.object, data) + self.assertEqual(exc.start, 3) + self.assertEqual(exc.end, 4) + + # Test IncrementalDecoder + dec = codecs.getincrementaldecoder("utf-8-sig")() + with self.assertRaises(UnicodeDecodeError) as cm: + dec.decode(data, final=True) + exc = cm.exception + self.assertEqual(exc.object, data) + self.assertEqual(exc.start, 3) + self.assertEqual(exc.end, 4) + + # Test StreamReader + reader = codecs.getreader("utf-8-sig")(io.BytesIO(data)) + with self.assertRaises(UnicodeDecodeError) as cm: + reader.read() + exc = cm.exception + self.assertEqual(exc.object, data) + self.assertEqual(exc.start, 3) + self.assertEqual(exc.end, 4) + + # Test custom error handler receives correct offsets + received = [] + def handler(err): + received.append((err.object, err.start, err.end)) + return ("?", err.end) + + old_handler = codecs.lookup_error("replace") + codecs.register_error("test_utf8sig_handler", handler) + try: + result = data.decode("utf-8-sig", "test_utf8sig_handler") + self.assertEqual(result, "?") + self.assertEqual(received, [(data, 3, 4)]) + finally: + # clean up error handler if needed + pass + class EscapeDecodeTest(unittest.TestCase): def test_empty(self): diff --git a/Misc/NEWS.d/next/Library/2026-10-10-00-37-12.gh-issue-159071.a62BQP.rst b/Misc/NEWS.d/next/Library/2026-10-10-00-37-12.gh-issue-159071.a62BQP.rst new file mode 100644 index 000000000000000..e665a6ce071bceb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-10-00-37-12.gh-issue-159071.a62BQP.rst @@ -0,0 +1,2 @@ +Fixed :exc:`UnicodeDecodeError` offsets in the :mod:`encodings.utf_8_sig` +codec when input starts with a BOM.