Repository: python-pillow/Pillow

Issue:
Malformed APP1 EXIF segment are not ignored
### What did you do?

Tried to open an image from a Samsung device with malformed exif data. I can't control how the device will take pictures. I can open the image successfully in Paintbrush and other image manipulation tools, but not in Apple preview. 

### What did you expect to happen?

To ignore the segment and let me work with them pixels please.

### What actually happened?

```
Traceback (most recent call last):
  File "/Users/deepio/Desktop/ape.py", line 7, in <module>
    image = Image.open(file)
  File "/Users/deepio/.pyenv/versions/3.10.4/lib/python3.10/site-packages/PIL/Image.py", line 3280, in open
    raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file 'bad_file.jpg'
```

### What are your OS, Python and Pillow versions?

* OS: ubuntu `22.04` and MacOS
* Python: `3.10.4`, `3.9`, ...
* Pillow: `10.0.1`

```python
from PIL import Image
file = "bad_file.jpg"

image = Image.open(file)
```

I tested to make sure that the scrubbed image still created the same issue and it does (bad_file2.jpg). I zipped the image just in-case github does some image manipulation on uploaded files, `bad_file2.jpg` is the same image with all white pixels and with the weird APP1 segment added.

[bad_file2.zip](https://github.com/python-pillow/Pillow/files/12869830/bad_file2.zip)




Discussion:
Is the image one that can be included in our test suite, and distributed under the Pillow license?
Yes, that's why I created the second image without personally identifiable information.

Resolving patch:
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -170,11 +170,19 @@ def APP(self, marker):
                 # 1 dpcm = 2.54 dpi
                 dpi *= 2.54
             self.info["dpi"] = dpi, dpi
-        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):
-            # SyntaxError for invalid/unreadable EXIF
+        except (
+            struct.error,
+            KeyError,
+            SyntaxError,
+            TypeError,
+            ValueError,
+            ZeroDivisionError,
+        ):
+            # struct.error for truncated EXIF
             # KeyError for dpi not included
-            # ZeroDivisionError for invalid dpi rational value
+            # SyntaxError for invalid/unreadable EXIF
             # ValueError or TypeError for dpi being an invalid float
+            # ZeroDivisionError for invalid dpi rational value
             self.info["dpi"] = 72, 72
 
 


Test patch:
diff --git a/Tests/images/truncated_exif_dpi.jpg b/Tests/images/truncated_exif_dpi.jpg
new file mode 100644
Binary files /dev/null and b/Tests/images/truncated_exif_dpi.jpg differ
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -767,6 +767,13 @@ def test_dpi_exif_string(self):
             # This should return the default
             assert im.info.get("dpi") == (72, 72)
 
+    def test_dpi_exif_truncated(self):
+        # Arrange
+        with Image.open("Tests/images/truncated_exif_dpi.jpg") as im:
+            # Act / Assert
+            # This should return the default
+            assert im.info.get("dpi") == (72, 72)
+
     def test_no_dpi_in_exif(self):
         # Arrange
         # This is photoshop-200dpi.jpg with resolution removed from EXIF: