Skip to content

Exclude formats with ! when opening#9564

Open
radarhere wants to merge 7 commits into
python-pillow:mainfrom
radarhere:open
Open

Exclude formats with ! when opening#9564
radarhere wants to merge 7 commits into
python-pillow:mainfrom
radarhere:open

Conversation

@radarhere

Copy link
Copy Markdown
Member

Currently, to prevent opening a file in a certain format, without limiting your access to any other format, you can

from PIL import Image
Image.init()
Image.open(path, formats=[f for f in Image.ID if f != "EPS"])

or

from PIL import Image, EpsImagePlugin
Image.ID.remove("EPS")
Image.open(path)

It would be nice if there was a simpler way. This PR suggests

from PIL import Image
Image.open(path, formats=["!EPS"])

And yes, I suggest that Image.open(path, formats=["EPS", "!EPS"]) still does not allow an EPS image to be opened.

@hugovk hugovk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so:

Image.open(path)  # tries all, default formats=None
Image.open(path, formats=None)  # tries all
Image.open(path, formats=["JPEG"])  # only tries JPEG
Image.open(path, formats=["!EPS"])  # tries all but EPS
Image.open(path, formats=["EPS", "!EPS"])  # tries all but EPS
Image.open(path, formats=["JPEG", "!EPS"])  # only tries JPEG, !EPS has no effect

That last one could be surprising, but it's probably okay.

Comment thread src/PIL/Image.py Outdated
Comment thread src/PIL/Image.py Outdated
Comment thread src/PIL/Image.py Outdated
Comment thread src/PIL/Image.py Outdated
radarhere and others added 3 commits April 16, 2026 00:12
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
@radarhere

Copy link
Copy Markdown
Member Author

Image.open(path, formats=["EPS", "!EPS"]) # tries all but EPS

I originally thought this should try none, but it's an edge case that could be debated. I've pushed your suggestions, and gone with trying all except EPS.

@hugovk

hugovk commented Jun 28, 2026

Copy link
Copy Markdown
Member

If someone is currently denying all formats with:

from PIL import Image
im = Image.open("Tests/images/hopper.png", formats=[])
print(im)

On main:

Traceback (most recent call last):
  File "/Users/hugo/github/Pillow/1.py", line 2, in <module>
    im = Image.open("Tests/images/hopper.png", formats=[])
  File "/Users/hugo/github/Pillow/src/PIL/Image.py", line 3715, in open
    raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file 'Tests/images/hopper.png'

With the PR:

<PIL.PngImagePlugin.PngImageFile image mode=RGB size=128x128 at 0x102CF5BE0>

This PR updates the security recommendation in docs/handbook/security.rst to reflect this behaviour change, but could this be unexpected?

@hugovk hugovk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, given a PNG file with .jpg extension, and only allowing formats=["JPEG"], and counting the preinit and init calls:

from PIL import Image

calls = []
preinit, init = Image.preinit, Image.init
Image.preinit = lambda: (calls.append("preinit"), preinit())[1]
Image.init    = lambda: (calls.append("init"),    init())[1]

try:
    # cp Tests/images/hopper.png /tmp/hopper_png.jpg
    Image.open("/tmp/hopper_png.jpg", formats=["JPEG"]).load()
except Image.UnidentifiedImageError:
    print("UnidentifiedImageError")

print(calls)
print("plugins loaded:", len(Image.ID), sorted(Image.ID))

On main, the lazy loading means the JPEG plugin is loaded, the file is rejected, the allowlist is exhausted, and it quickly gives up:

UnidentifiedImageError
[]
plugins loaded: 1 ['JPEG']

With this PR, the fallback runs anyway, both preinit and init are called and all the plugins are loaded for nothing:

UnidentifiedImageError
['preinit', 'init']
plugins loaded: 43 ['AVIF', 'BLP', 'BMP', 'BUFR', 'CUR', 'DCX', 'DDS', 'DIB', 'EPS', 'FITS', 'FLI', 'FTEX', 'GBR', 'GIF', 'GRIB', 'HDF5', 'ICNS', 'ICO', 'IM', 'IMT', 'IPTC', 'JPEG', 'JPEG2000', 'MCIDAS', 'MPEG', 'MSP', 'PCD', 'PCX', 'PIXAR', 'PNG', 'PPM', 'PSD', 'QOI', 'SGI', 'SPIDER', 'SUN', 'TGA', 'TIFF', 'WEBP', 'WMF', 'XBM', 'XPM', 'XVTHUMB']

Comment thread Tests/test_image.py
assert im.size == (128, 128)

@pytest.mark.parametrize("formats", (("!PNG",), ("PNG", "!PNG"), ("JPEG", "!PNG")))
def test_open_formats_exclude(self, formats: tuple[str]) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_open_formats_exclude(self, formats: tuple[str]) -> None:
def test_open_formats_exclude(self, formats: tuple[str, ...]) -> None:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants