Skip to content

Commit c9ec76a

Browse files
committed
Raise FileNotFoundError if show_file() path does not exist
1 parent b48d175 commit c9ec76a

File tree

3 files changed

+46
-26
lines changed

3 files changed

+46
-26
lines changed

Tests/test_imageshow.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
from typing import Any
45

56
import pytest
@@ -65,6 +66,27 @@ def test_show_without_viewers() -> None:
6566
ImageShow._viewers = viewers
6667

6768

69+
@pytest.mark.parametrize(
70+
"viewer",
71+
(
72+
ImageShow.Viewer(),
73+
ImageShow.WindowsViewer(),
74+
ImageShow.MacViewer(),
75+
ImageShow.XDGViewer(),
76+
ImageShow.DisplayViewer(),
77+
ImageShow.GmDisplayViewer(),
78+
ImageShow.EogViewer(),
79+
ImageShow.XVViewer(),
80+
ImageShow.IPythonViewer(),
81+
),
82+
)
83+
def test_show_file(viewer: ImageShow.Viewer) -> None:
84+
assert not os.path.exists("missing.png")
85+
86+
with pytest.raises(FileNotFoundError):
87+
viewer.show_file("missing.png")
88+
89+
6890
def test_viewer() -> None:
6991
viewer = ImageShow.Viewer()
7092

docs/releasenotes/10.4.0.rst

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,16 @@
44
Security
55
========
66

7-
TODO
8-
^^^^
9-
10-
TODO
11-
12-
:cve:`YYYY-XXXXX`: TODO
13-
^^^^^^^^^^^^^^^^^^^^^^^
14-
15-
TODO
7+
ImageShow.WindowsViewer.show_file
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169

17-
Backwards Incompatible Changes
18-
==============================
10+
If an attacker has control over the ``path`` passed to
11+
``ImageShow.WindowsViewer.show_file()``, they may be able to
12+
execute arbitrary shell commands.
1913

20-
TODO
21-
^^^^
14+
To prevent this, a :py:exc:`FileNotFoundError` will be raised if the ``path``
15+
does not exist as a file. To provide a consistent experience, the error has
16+
been added to all :py:class:`~PIL.ImageShow` viewers.
2217

2318
Deprecations
2419
============
@@ -46,14 +41,6 @@ ImageDraw.getdraw hints parameter
4641

4742
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
4843

49-
API Changes
50-
===========
51-
52-
TODO
53-
^^^^
54-
55-
TODO
56-
5744
API Additions
5845
=============
5946

@@ -64,11 +51,6 @@ Added :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functiona
6451
:py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it
6552
takes a center point and radius.
6653

67-
TODO
68-
^^^^
69-
70-
TODO
71-
7254
Other Changes
7355
=============
7456

src/PIL/ImageShow.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def show_file(self, path: str, **options: Any) -> int:
118118
"""
119119
Display given file.
120120
"""
121+
if not os.path.exists(path):
122+
raise FileNotFoundError
121123
os.system(self.get_command(path, **options)) # nosec
122124
return 1
123125

@@ -142,6 +144,8 @@ def show_file(self, path: str, **options: Any) -> int:
142144
"""
143145
Display given file.
144146
"""
147+
if not os.path.exists(path):
148+
raise FileNotFoundError
145149
subprocess.Popen(
146150
self.get_command(path, **options),
147151
shell=True,
@@ -171,6 +175,8 @@ def show_file(self, path: str, **options: Any) -> int:
171175
"""
172176
Display given file.
173177
"""
178+
if not os.path.exists(path):
179+
raise FileNotFoundError
174180
subprocess.call(["open", "-a", "Preview.app", path])
175181
executable = sys.executable or shutil.which("python3")
176182
if executable:
@@ -215,6 +221,8 @@ def show_file(self, path: str, **options: Any) -> int:
215221
"""
216222
Display given file.
217223
"""
224+
if not os.path.exists(path):
225+
raise FileNotFoundError
218226
subprocess.Popen(["xdg-open", path])
219227
return 1
220228

@@ -237,6 +245,8 @@ def show_file(self, path: str, **options: Any) -> int:
237245
"""
238246
Display given file.
239247
"""
248+
if not os.path.exists(path):
249+
raise FileNotFoundError
240250
args = ["display"]
241251
title = options.get("title")
242252
if title:
@@ -259,6 +269,8 @@ def show_file(self, path: str, **options: Any) -> int:
259269
"""
260270
Display given file.
261271
"""
272+
if not os.path.exists(path):
273+
raise FileNotFoundError
262274
subprocess.Popen(["gm", "display", path])
263275
return 1
264276

@@ -275,6 +287,8 @@ def show_file(self, path: str, **options: Any) -> int:
275287
"""
276288
Display given file.
277289
"""
290+
if not os.path.exists(path):
291+
raise FileNotFoundError
278292
subprocess.Popen(["eog", "-n", path])
279293
return 1
280294

@@ -299,6 +313,8 @@ def show_file(self, path: str, **options: Any) -> int:
299313
"""
300314
Display given file.
301315
"""
316+
if not os.path.exists(path):
317+
raise FileNotFoundError
302318
args = ["xv"]
303319
title = options.get("title")
304320
if title:

0 commit comments

Comments
 (0)