From 0d9c2d4f0d8cb6d328ee26c8eff504a1d00fcb6c Mon Sep 17 00:00:00 2001 From: Guillaume Marty Date: Mon, 26 Oct 2026 11:30:58 +0100 Subject: [PATCH 1/3] Raise SyntaxError for non-numeric XBM #define values Co-Authored-By: Claude Opus 5.5 (1M context) --- Tests/test_file_xbm.py | 7 +++++++ src/PIL/XbmImagePlugin.py | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 2cf8d28e7e1..3ab8ccd05be 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -111,6 +111,13 @@ def test_invalid_file() -> None: XbmImagePlugin.XbmImageFile(invalid_file) +def test_non_numeric_define() -> None: + # A C header that is not an XBM image should not raise a ValueError + b = BytesIO(b"#define FOO bar\nint x;\n") + with pytest.raises(UnidentifiedImageError): + Image.open(b) + + def test_save_wrong_mode(tmp_path: Path) -> None: im = hopper() out = tmp_path / "temp.xbm" diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index 69ce54ebf19..4ffac07dd03 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -80,7 +80,11 @@ def _open(self) -> None: if line.endswith(b"_bits[] = {"): offset = self.fp.tell() break - value = int(line.split().pop()) + try: + value = int(line.split().pop()) + except ValueError as e: + msg = "not a XBM file" + raise SyntaxError(msg) from e if b"width " in line: width = value elif b"height " in line: From 504802325d3cb21d211820e32fc3fb879a4e8e54 Mon Sep 17 00:00:00 2001 From: BastienGimbert Date: Mon, 26 Oct 2026 22:08:08 +0100 Subject: [PATCH 2/3] Tests: Add regression test for XBM incremental parsing --- Tests/test_file_xbm.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 3ab8ccd05be..8f6e6c85358 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -4,7 +4,7 @@ import pytest -from PIL import Image, UnidentifiedImageError, XbmImagePlugin +from PIL import Image, ImageFile, UnidentifiedImageError, XbmImagePlugin from .helper import assert_image_equal, hopper, timeout_unless_slower_valgrind @@ -118,6 +118,17 @@ def test_non_numeric_define() -> None: Image.open(b) +def test_parser_streaming() -> None: + # Test incremental parsing to ensure chunks don't cause ValueError + p = ImageFile.Parser() + for i in range(0, len(PIL151), 8): + p.feed(PIL151[i : i + 8]) + + with p.close() as im: + with Image.open(BytesIO(PIL151)) as expected: + assert_image_equal(im, expected) + + def test_save_wrong_mode(tmp_path: Path) -> None: im = hopper() out = tmp_path / "temp.xbm" From bb372e168a8f6d9488ccabf320e3aad0c8ad9cb3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 28 Oct 2026 20:18:23 +1100 Subject: [PATCH 3/3] Simplify code --- Tests/test_file_xbm.py | 22 ++++------------------ src/PIL/XbmImagePlugin.py | 5 ++--- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 8f6e6c85358..3a0729c5bce 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -4,7 +4,7 @@ import pytest -from PIL import Image, ImageFile, UnidentifiedImageError, XbmImagePlugin +from PIL import Image, UnidentifiedImageError, XbmImagePlugin from .helper import assert_image_equal, hopper, timeout_unless_slower_valgrind @@ -110,23 +110,9 @@ def test_invalid_file() -> None: with pytest.raises(SyntaxError): XbmImagePlugin.XbmImageFile(invalid_file) - -def test_non_numeric_define() -> None: - # A C header that is not an XBM image should not raise a ValueError - b = BytesIO(b"#define FOO bar\nint x;\n") - with pytest.raises(UnidentifiedImageError): - Image.open(b) - - -def test_parser_streaming() -> None: - # Test incremental parsing to ensure chunks don't cause ValueError - p = ImageFile.Parser() - for i in range(0, len(PIL151), 8): - p.feed(PIL151[i : i + 8]) - - with p.close() as im: - with Image.open(BytesIO(PIL151)) as expected: - assert_image_equal(im, expected) + b = BytesIO(b"#define basic_width invalidwidth") + with pytest.raises(SyntaxError): + XbmImagePlugin.XbmImageFile(b) def test_save_wrong_mode(tmp_path: Path) -> None: diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index 4ffac07dd03..e1818fac91c 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -82,9 +82,8 @@ def _open(self) -> None: break try: value = int(line.split().pop()) - except ValueError as e: - msg = "not a XBM file" - raise SyntaxError(msg) from e + except ValueError: + break if b"width " in line: width = value elif b"height " in line: