From 804fe7bbf86826a34067846b8ca29837ccc8d2af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 May 2024 17:50:50 +1000 Subject: [PATCH] Do not read layers when opening --- Tests/test_file_psd.py | 24 +++++++++++++++--------- src/PIL/PsdImagePlugin.py | 30 +++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 484a1be8f83..91539e0e4a3 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -4,7 +4,7 @@ import pytest -from PIL import Image, PsdImagePlugin, UnidentifiedImageError +from PIL import Image, PsdImagePlugin from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy @@ -150,14 +150,6 @@ def test_combined_larger_than_size() -> None: @pytest.mark.parametrize( "test_file,raises", [ - ( - "Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", - UnidentifiedImageError, - ), - ( - "Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", - UnidentifiedImageError, - ), ("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError), ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), ], @@ -167,3 +159,17 @@ def test_crashes(test_file: str, raises) -> None: with pytest.raises(raises): with Image.open(f): pass + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", + "Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", + ], +) +def test_layer_crashes(test_file: str) -> None: + with open(test_file, "rb") as f: + with Image.open(f) as im: + with pytest.raises(SyntaxError): + im.layers diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 86c1a67630e..edf698bf02d 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -18,6 +18,7 @@ from __future__ import annotations import io +from functools import cached_property from . import Image, ImageFile, ImagePalette from ._binary import i8 @@ -118,18 +119,17 @@ def _open(self) -> None: # # layer and mask information - self.layers = [] + self._layers_position = None size = i32(read(4)) if size: end = self.fp.tell() + size size = i32(read(4)) if size: - _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size)) - self.layers = _layerinfo(_layer_data, size) + self._layers_position = self.fp.tell() + self._layers_size = size self.fp.seek(end) - self.n_frames = len(self.layers) - self.is_animated = self.n_frames > 1 + self._n_frames: int | None = None # # image descriptor @@ -141,6 +141,26 @@ def _open(self) -> None: self.frame = 1 self._min_frame = 1 + @cached_property + def layers(self): + layers = [] + if self._layers_position is not None: + self._fp.seek(self._layers_position) + _layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size)) + layers = _layerinfo(_layer_data, self._layers_size) + self._n_frames = len(layers) + return layers + + @property + def n_frames(self) -> int: + if self._n_frames is None: + self._n_frames = len(self.layers) + return self._n_frames + + @property + def is_animated(self) -> bool: + return len(self.layers) > 1 + def seek(self, layer: int) -> None: if not self._seek_check(layer): return