Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise ValueError when trying to access internal fp after close #6213

Merged
merged 4 commits into from May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions Tests/test_file_apng.py
Expand Up @@ -637,6 +637,15 @@ def test_apng_save_blend(tmp_path):
assert im.getpixel((0, 0)) == (0, 255, 0, 255)


def test_seek_after_close():
im = Image.open("Tests/images/apng/delay.png")
im.seek(1)
im.close()

with pytest.raises(ValueError):
im.seek(0)


def test_constants_deprecation():
for enum, prefix in {
PngImagePlugin.Disposal: "APNG_DISPOSE_",
Expand Down
9 changes: 9 additions & 0 deletions Tests/test_file_fli.py
Expand Up @@ -46,6 +46,15 @@ def test_closed_file():
im.close()


def test_seek_after_close():
im = Image.open(animated_test_file)
im.seek(1)
im.close()

with pytest.raises(ValueError):
im.seek(0)


def test_context_manager():
with warnings.catch_warnings():
with Image.open(static_test_file) as im:
Expand Down
13 changes: 13 additions & 0 deletions Tests/test_file_gif.py
Expand Up @@ -46,6 +46,19 @@ def test_closed_file():
im.close()


def test_seek_after_close():
im = Image.open("Tests/images/iss634.gif")
im.load()
im.close()

with pytest.raises(ValueError):
im.is_animated
with pytest.raises(ValueError):
im.n_frames
with pytest.raises(ValueError):
im.seek(1)


def test_context_manager():
with warnings.catch_warnings():
with Image.open(TEST_GIF) as im:
Expand Down
8 changes: 8 additions & 0 deletions Tests/test_file_mpo.py
Expand Up @@ -48,6 +48,14 @@ def test_closed_file():
im.close()


def test_seek_after_close():
im = Image.open(test_files[0])
im.close()

with pytest.raises(ValueError):
im.seek(1)


def test_context_manager():
with warnings.catch_warnings():
with Image.open(test_files[0]) as im:
Expand Down
9 changes: 9 additions & 0 deletions Tests/test_file_tiff.py
Expand Up @@ -70,6 +70,15 @@ def test_closed_file(self):
im.load()
im.close()

def test_seek_after_close(self):
im = Image.open("Tests/images/multipage.tiff")
im.close()

with pytest.raises(ValueError):
im.n_frames
with pytest.raises(ValueError):
im.seek(1)

def test_context_manager(self):
with warnings.catch_warnings():
with Image.open("Tests/images/multipage.tiff") as im:
Expand Down
13 changes: 2 additions & 11 deletions src/PIL/DcxImagePlugin.py
Expand Up @@ -57,7 +57,7 @@ def _open(self):
break
self._offset.append(offset)

self.__fp = self.fp
self._fp = self.fp
self.frame = None
self.n_frames = len(self._offset)
self.is_animated = self.n_frames > 1
Expand All @@ -67,22 +67,13 @@ def seek(self, frame):
if not self._seek_check(frame):
return
self.frame = frame
self.fp = self.__fp
self.fp = self._fp
self.fp.seek(self._offset[frame])
PcxImageFile._open(self)

def tell(self):
return self.frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


Image.register_open(DcxImageFile.format, DcxImageFile, _accept)

Expand Down
15 changes: 3 additions & 12 deletions src/PIL/FliImagePlugin.py
Expand Up @@ -91,7 +91,7 @@ def _open(self):

# set things up to decode first frame
self.__frame = -1
self.__fp = self.fp
self._fp = self.fp
self.__rewind = self.fp.tell()
self.seek(0)

Expand Down Expand Up @@ -125,7 +125,7 @@ def seek(self, frame):
def _seek(self, frame):
if frame == 0:
self.__frame = -1
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.__offset = 128
else:
# ensure that the previous frame was loaded
Expand All @@ -136,7 +136,7 @@ def _seek(self, frame):
self.__frame = frame

# move to next frame
self.fp = self.__fp
self.fp = self._fp
self.fp.seek(self.__offset)

s = self.fp.read(4)
Expand All @@ -153,15 +153,6 @@ def _seek(self, frame):
def tell(self):
return self.__frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


#
# registry
Expand Down
17 changes: 4 additions & 13 deletions src/PIL/GifImagePlugin.py
Expand Up @@ -102,7 +102,7 @@ def _open(self):
p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p

self.__fp = self.fp # FIXME: hack
self._fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
Expand Down Expand Up @@ -161,7 +161,7 @@ def _seek(self, frame, update_image=True):
self.__offset = 0
self.dispose = None
self.__frame = -1
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.disposal_method = 0
else:
# ensure that the previous frame was loaded
Expand All @@ -171,7 +171,7 @@ def _seek(self, frame, update_image=True):
if frame != self.__frame + 1:
raise ValueError(f"cannot seek to frame {frame}")

self.fp = self.__fp
self.fp = self._fp
if self.__offset:
# backup to last frame
self.fp.seek(self.__offset)
Expand Down Expand Up @@ -281,7 +281,7 @@ def _seek(self, frame, update_image=True):
s = None

if interlace is None:
# self.__fp = None
# self._fp = None
raise EOFError
if not update_image:
return
Expand Down Expand Up @@ -443,15 +443,6 @@ def load_end(self):
def tell(self):
return self.__frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


# --------------------------------------------------------------------
# Write GIF files
Expand Down
13 changes: 2 additions & 11 deletions src/PIL/ImImagePlugin.py
Expand Up @@ -245,7 +245,7 @@ def _open(self):

self.__offset = offs = self.fp.tell()

self.__fp = self.fp # FIXME: hack
self._fp = self.fp # FIXME: hack

if self.rawmode[:2] == "F;":

Expand Down Expand Up @@ -294,22 +294,13 @@ def seek(self, frame):
size = ((self.size[0] * bits + 7) // 8) * self.size[1]
offs = self.__offset + frame * size

self.fp = self.__fp
self.fp = self._fp

self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]

def tell(self):
return self.frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


#
# --------------------------------------------------------------------
Expand Down
12 changes: 8 additions & 4 deletions src/PIL/Image.py
Expand Up @@ -544,8 +544,10 @@ def __enter__(self):

def __exit__(self, *args):
if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
if hasattr(self, "_close__fp"):
self._close__fp()
if getattr(self, "_fp", False):
if self._fp != self.fp:
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
self.fp = None
Expand All @@ -563,8 +565,10 @@ def close(self):
more information.
"""
try:
if hasattr(self, "_close__fp"):
self._close__fp()
if getattr(self, "_fp", False):
if self._fp != self.fp:
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
self.fp = None
Expand Down
10 changes: 0 additions & 10 deletions src/PIL/MicImagePlugin.py
Expand Up @@ -62,7 +62,6 @@ def _open(self):
if not self.images:
raise SyntaxError("not an MIC file; no image entries")

self.__fp = self.fp
self.frame = None
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
Expand All @@ -89,15 +88,6 @@ def seek(self, frame):
def tell(self):
return self.frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


#
# --------------------------------------------------------------------
Expand Down
17 changes: 4 additions & 13 deletions src/PIL/MpoImagePlugin.py
Expand Up @@ -58,20 +58,20 @@ def _after_jpeg_open(self, mpheader=None):
assert self.n_frames == len(self.__mpoffsets)
del self.info["mpoffset"] # no longer needed
self.is_animated = self.n_frames > 1
self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self._fp = self.fp # FIXME: hack
self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0
self.offset = 0
# for now we can only handle reading and individual frame extraction
self.readonly = 1

def load_seek(self, pos):
self.__fp.seek(pos)
self._fp.seek(pos)

def seek(self, frame):
if not self._seek_check(frame):
return
self.fp = self.__fp
self.fp = self._fp
self.offset = self.__mpoffsets[frame]

self.fp.seek(self.offset + 2) # skip SOI marker
Expand All @@ -97,15 +97,6 @@ def seek(self, frame):
def tell(self):
return self.__frame

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None

@staticmethod
def adopt(jpeg_instance, mpheader=None):
"""
Expand Down
19 changes: 5 additions & 14 deletions src/PIL/PngImagePlugin.py
Expand Up @@ -689,7 +689,7 @@ def _open(self):

if not _accept(self.fp.read(8)):
raise SyntaxError("not a PNG file")
self.__fp = self.fp
self._fp = self.fp
self.__frame = 0

#
Expand Down Expand Up @@ -746,7 +746,7 @@ def _open(self):
self._close_exclusive_fp_after_loading = False
self.png.save_rewind()
self.__rewind_idat = self.__prepare_idat
self.__rewind = self.__fp.tell()
self.__rewind = self._fp.tell()
if self.default_image:
# IDAT chunk contains default image and not first animation frame
self.n_frames += 1
Expand Down Expand Up @@ -801,15 +801,15 @@ def seek(self, frame):
def _seek(self, frame, rewind=False):
if frame == 0:
if rewind:
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.png.rewind()
self.__prepare_idat = self.__rewind_idat
self.im = None
if self.pyaccess:
self.pyaccess = None
self.info = self.png.im_info
self.tile = self.png.im_tile
self.fp = self.__fp
self.fp = self._fp
self._prev_im = None
self.dispose = None
self.default_image = self.info.get("default_image", False)
Expand All @@ -828,7 +828,7 @@ def _seek(self, frame, rewind=False):
self.im.paste(self.dispose, self.dispose_extent)
self._prev_im = self.im.copy()

self.fp = self.__fp
self.fp = self._fp

# advance to the next frame
if self.__prepare_idat:
Expand Down Expand Up @@ -1006,15 +1006,6 @@ def getxmp(self):
else {}
)

def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None


# --------------------------------------------------------------------
# PNG writer
Expand Down