Skip to content

Commit

Permalink
Merge pull request #8560 from Knio/tom/bufferedio
Browse files Browse the repository at this point in the history
Fix file position desync when calling into libtiff
  • Loading branch information
radarhere authored Nov 29, 2024
2 parents b1a9199 + 9fd4450 commit 1c15225
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 8 deletions.
19 changes: 19 additions & 0 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,25 @@ def test_exif_transpose(self) -> None:

assert_image_similar(base_im, im, 0.7)

@pytest.mark.parametrize(
"test_file",
[
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
"Tests/images/old-style-jpeg-compression.tif",
],
)
def test_buffering(self, test_file: str) -> None:
# load exif first
with Image.open(open(test_file, "rb", buffering=1048576)) as im:
exif = dict(im.getexif())

# load image before exif
with Image.open(open(test_file, "rb", buffering=1048576)) as im2:
im2.load()
exif_after_load = dict(im2.getexif())

assert exif == exif_after_load

@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
def test_sampleformat_not_corrupted(self) -> None:
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
Expand Down
17 changes: 9 additions & 8 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1216,10 +1216,6 @@ def seek(self, frame: int) -> None:
def _seek(self, frame: int) -> None:
self.fp = self._fp

# reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()

while len(self._frame_pos) <= frame:
if not self.__next:
msg = "no more images in TIFF file"
Expand Down Expand Up @@ -1303,10 +1299,6 @@ def load_end(self) -> None:
if not self.is_animated:
self._close_exclusive_fp_after_loading = True

# reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()

# load IFD data from fp before it is closed
exif = self.getexif()
for key in TiffTags.TAGS_V2_GROUPS:
Expand Down Expand Up @@ -1381,8 +1373,17 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None:
logger.debug("have fileno, calling fileno version of the decoder.")
if not close_self_fp:
self.fp.seek(0)
# Save and restore the file position, because libtiff will move it
# outside of the Python runtime, and that will confuse
# io.BufferedReader and possible others.
# NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(),
# because the buffer read head already may not equal the actual
# file position, and fp.seek() may just adjust it's internal
# pointer and not actually seek the OS file handle.
pos = os.lseek(fp, 0, os.SEEK_CUR)
# 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
os.lseek(fp, pos, os.SEEK_SET)
else:
# we have something else.
logger.debug("don't have fileno or getvalue. just reading")
Expand Down

0 comments on commit 1c15225

Please sign in to comment.