From fd3f6c1a929e52332ac43fdbd328d767b9301839 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Jun 2024 19:31:40 +1000 Subject: [PATCH] Remove zero-byte end padding when parsing any XMP data --- Tests/test_image.py | 22 ++++++++++++++++++++++ src/PIL/Image.py | 2 +- src/PIL/JpegImagePlugin.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 5b1ba6289f4..c5cf5b38331 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -8,6 +8,7 @@ import tempfile import warnings from pathlib import Path +from types import ModuleType from typing import IO, Any import pytest @@ -35,6 +36,12 @@ skip_unless_feature, ) +ElementTree: ModuleType | None +try: + from defusedxml import ElementTree +except ImportError: + ElementTree = None + # Deprecation helper def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image: @@ -921,6 +928,21 @@ def test_empty_xmp(self) -> None: with Image.open("Tests/images/hopper.gif") as im: assert im.getxmp() == {} + def test_getxmp_padded(self) -> None: + im = Image.new("RGB", (1, 1)) + im.info["xmp"] = ( + b'\n' + b'\n\x00\x00' + ) + if ElementTree is None: + with pytest.warns( + UserWarning, + match="XMP data cannot be read without defusedxml dependency", + ): + assert im.getxmp() == {} + else: + assert im.getxmp() == {"xmpmeta": None} + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) def test_zero_tobytes(self, size: tuple[int, int]) -> None: im = Image.new("RGB", size) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e9fb55d6b7a..cbeefa189a5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1511,7 +1511,7 @@ def get_value(element): return {} if "xmp" not in self.info: return {} - root = ElementTree.fromstring(self.info["xmp"]) + root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00")) return {get_name(root.tag): get_value(root)} def getexif(self) -> Exif: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index b7d0d24f242..ada52915373 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -96,7 +96,7 @@ def APP(self, marker): self.info["exif"] = s self._exif_offset = self.fp.tell() - n + 6 elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00": - self.info["xmp"] = s.split(b"\x00")[1] + self.info["xmp"] = s.split(b"\x00", 1)[1] elif marker == 0xFFE2 and s[:5] == b"FPXR\0": # extract FlashPix information (incomplete) self.info["flashpix"] = s # FIXME: value will change