Describe the bug
Images with EXIF Orientation (e.g. Orientation=6 / Rotate 90 CW) are already physically rotated when opened by Pillow, but plone.namedfile still applies its own EXIF-based rotation logic.
This results in inconsistent image handling and incorrect assumptions about image dimensions during upload/import.
During debugging it appears that PIL.Image.open() may already return an image with pixels rotated according to EXIF Orientation, while the EXIF metadata still reports a non-normalized orientation.
plone.namedfile then applies an additional rotation step based on EXIF, leading to double-handling or no-op rotations depending on geometry.
To Reproduce
- Take a JPEG image with EXIF
Orientation = 6 (portrait photo stored as landscape + EXIF).
- Upload the image to Plone (Classic UI or Volto).
- Debug the image handling code in
plone.namedfile, inspecting:
PIL.Image.open(BytesIO(data))
img.size
- EXIF Orientation before any explicit rotation.
- Observe that:
img.size already reflects a portrait image.
- EXIF Orientation is still
6.
plone.namedfile rotation logic is still executed.
Expected behavior
Plone should either:
- Explicitly normalize EXIF orientation using
ImageOps.exif_transpose() and then reset Orientation to 1, or
- Detect that Pillow has already applied EXIF orientation and skip additional rotation logic.
In any case, image rotation should be applied exactly once, and image dimensions should be consistent with the applied transformation.
Screenshots
Not applicable (debug-level issue).
Software (please complete the following information):
- OS: Linux
- Browser: Any
- Plone Version: 6.0.15
- Volto Version: 17.22.3
- Pillow 11.0.0
- plone.namedfile 7.0.1
Additional context
A first inspection of the current code suggests that this behavior is not limited to older versions.
When debugging the upload pipeline, PIL.Image.open() loads the image using JpegImagePlugin (as expected for JPEG files) and returns an Image instance whose pixel data already appears to be rotated according to the EXIF Orientation value.
In this scenario:
- the image geometry (
img.size) already reflects the rotated image,
- EXIF metadata still reports a non-normalized Orientation value (e.g. 6),
plone.namedfile then applies its own EXIF-based rotation logic.
This indicates that, at least in some Pillow versions/configurations, EXIF orientation may already be applied automatically when loading JPEG images, making the additional rotation logic in plone.namedfile redundant or incorrect.
Caution
Do not file security issues in this tracker.
Send email to security@plone.org instead.
See Report a security issue.
Describe the bug
Images with EXIF Orientation (e.g. Orientation=6 / Rotate 90 CW) are already physically rotated when opened by Pillow, but
plone.namedfilestill applies its own EXIF-based rotation logic.This results in inconsistent image handling and incorrect assumptions about image dimensions during upload/import.
During debugging it appears that
PIL.Image.open()may already return an image with pixels rotated according to EXIF Orientation, while the EXIF metadata still reports a non-normalized orientation.plone.namedfilethen applies an additional rotation step based on EXIF, leading to double-handling or no-op rotations depending on geometry.To Reproduce
Orientation = 6(portrait photo stored as landscape + EXIF).plone.namedfile, inspecting:PIL.Image.open(BytesIO(data))img.sizeimg.sizealready reflects a portrait image.6.plone.namedfilerotation logic is still executed.Expected behavior
Plone should either:
ImageOps.exif_transpose()and then reset Orientation to1, orIn any case, image rotation should be applied exactly once, and image dimensions should be consistent with the applied transformation.
Screenshots
Not applicable (debug-level issue).
Software (please complete the following information):
Additional context
A first inspection of the current code suggests that this behavior is not limited to older versions.
When debugging the upload pipeline,
PIL.Image.open()loads the image usingJpegImagePlugin(as expected for JPEG files) and returns anImageinstance whose pixel data already appears to be rotated according to the EXIF Orientation value.In this scenario:
img.size) already reflects the rotated image,plone.namedfilethen applies its own EXIF-based rotation logic.This indicates that, at least in some Pillow versions/configurations, EXIF orientation may already be applied automatically when loading JPEG images, making the additional rotation logic in
plone.namedfileredundant or incorrect.