Fix file position desync when calling into libtiff

This commit is contained in:
Tom Flanagan 2024-11-19 20:40:14 -08:00
parent 0995305eb4
commit c7a9582eab
2 changed files with 32 additions and 8 deletions

View File

@ -0,0 +1,23 @@
from __future__ import annotations
import pytest
from PIL import Image
@pytest.mark.parametrize('test_file', [
'Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif',
'Tests/images/old-style-jpeg-compression.tif',
])
def test_libtiff_exif_loading(test_file) -> None:
# loading image before exif
im1 = Image.open(open(test_file, 'rb', buffering=1048576))
im1.load()
exif1 = dict(im1.getexif())
# loading exif before image
im2 = Image.open(open(test_file, 'rb', buffering=1048576))
exif2 = dict(im2.getexif())
assert exif1 == exif2

View File

@ -1216,10 +1216,6 @@ class TiffImageFile(ImageFile.ImageFile):
def _seek(self, frame: int) -> None: def _seek(self, frame: int) -> None:
self.fp = self._fp 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: while len(self._frame_pos) <= frame:
if not self.__next: if not self.__next:
msg = "no more images in TIFF file" msg = "no more images in TIFF file"
@ -1303,10 +1299,6 @@ class TiffImageFile(ImageFile.ImageFile):
if not self.is_animated: if not self.is_animated:
self._close_exclusive_fp_after_loading = True 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 # load IFD data from fp before it is closed
exif = self.getexif() exif = self.getexif()
for key in TiffTags.TAGS_V2_GROUPS: for key in TiffTags.TAGS_V2_GROUPS:
@ -1381,8 +1373,17 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("have fileno, calling fileno version of the decoder.") logger.debug("have fileno, calling fileno version of the decoder.")
if not close_self_fp: if not close_self_fp:
self.fp.seek(0) 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 # 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp") n, err = decoder.decode(b"fpfp")
os.lseek(fp, pos, os.SEEK_SET)
else: else:
# we have something else. # we have something else.
logger.debug("don't have fileno or getvalue. just reading") logger.debug("don't have fileno or getvalue. just reading")