Skip to content

Commit 1c15225

Browse files
authored
Merge pull request #8560 from Knio/tom/bufferedio
Fix file position desync when calling into libtiff
2 parents b1a9199 + 9fd4450 commit 1c15225

File tree

2 files changed

+28
-8
lines changed

2 files changed

+28
-8
lines changed

Tests/test_file_libtiff.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,25 @@ def test_exif_transpose(self) -> None:
10981098

10991099
assert_image_similar(base_im, im, 0.7)
11001100

1101+
@pytest.mark.parametrize(
1102+
"test_file",
1103+
[
1104+
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
1105+
"Tests/images/old-style-jpeg-compression.tif",
1106+
],
1107+
)
1108+
def test_buffering(self, test_file: str) -> None:
1109+
# load exif first
1110+
with Image.open(open(test_file, "rb", buffering=1048576)) as im:
1111+
exif = dict(im.getexif())
1112+
1113+
# load image before exif
1114+
with Image.open(open(test_file, "rb", buffering=1048576)) as im2:
1115+
im2.load()
1116+
exif_after_load = dict(im2.getexif())
1117+
1118+
assert exif == exif_after_load
1119+
11011120
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
11021121
def test_sampleformat_not_corrupted(self) -> None:
11031122
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted

src/PIL/TiffImagePlugin.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,10 +1216,6 @@ def seek(self, frame: int) -> None:
12161216
def _seek(self, frame: int) -> None:
12171217
self.fp = self._fp
12181218

1219-
# reset buffered io handle in case fp
1220-
# was passed to libtiff, invalidating the buffer
1221-
self.fp.tell()
1222-
12231219
while len(self._frame_pos) <= frame:
12241220
if not self.__next:
12251221
msg = "no more images in TIFF file"
@@ -1303,10 +1299,6 @@ def load_end(self) -> None:
13031299
if not self.is_animated:
13041300
self._close_exclusive_fp_after_loading = True
13051301

1306-
# reset buffered io handle in case fp
1307-
# was passed to libtiff, invalidating the buffer
1308-
self.fp.tell()
1309-
13101302
# load IFD data from fp before it is closed
13111303
exif = self.getexif()
13121304
for key in TiffTags.TAGS_V2_GROUPS:
@@ -1381,8 +1373,17 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None:
13811373
logger.debug("have fileno, calling fileno version of the decoder.")
13821374
if not close_self_fp:
13831375
self.fp.seek(0)
1376+
# Save and restore the file position, because libtiff will move it
1377+
# outside of the Python runtime, and that will confuse
1378+
# io.BufferedReader and possible others.
1379+
# NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(),
1380+
# because the buffer read head already may not equal the actual
1381+
# file position, and fp.seek() may just adjust it's internal
1382+
# pointer and not actually seek the OS file handle.
1383+
pos = os.lseek(fp, 0, os.SEEK_CUR)
13841384
# 4 bytes, otherwise the trace might error out
13851385
n, err = decoder.decode(b"fpfp")
1386+
os.lseek(fp, pos, os.SEEK_SET)
13861387
else:
13871388
# we have something else.
13881389
logger.debug("don't have fileno or getvalue. just reading")

0 commit comments

Comments
 (0)