Repository: python-pillow/Pillow

Issue:
Cannot open untruncated png image with LOAD_TRUNCATED_IMAGES=True
### What did you do?
Open an untruncated png image with LOAD_TRUNCATED_IMAGES==True.
### What did you expect to happen?
The image is opened.
### What actually happened?
The image is not opened. (But image converted with gimp opens)
```
Loaded PIL.ImageFile.LOAD_TRUNCATED_IMAGES=False
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/.virtualenvs/venv/lib64/python3.8/site-packages/PIL/PngImagePlugin.py in load_end(self)
    939             try:
--> 940                 self.png.call(cid, pos, length)
    941             except UnicodeDecodeError:

~/.virtualenvs/venv/lib64/python3.8/site-packages/PIL/PngImagePlugin.py in call(self, cid, pos, length)
    186         logger.debug("STREAM %r %s %s", cid, pos, length)
--> 187         return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
    188 

AttributeError: 'PngStream' object has no attribute 'chunk_I'

During handling of the above exception, another exception occurred:

OSError                                   Traceback (most recent call last)
<ipython-input-14-39acebf157ec> in <module>
      9 
     10 img = PIL.Image.open(image_path)
---> 11 img.load()
     12 print(f"Loaded {PIL.ImageFile.LOAD_TRUNCATED_IMAGES=}")

~/.virtualenvs/venv/lib64/python3.8/site-packages/PIL/ImageFile.py in load(self)
    264         self.readonly = readonly
    265 
--> 266         self.load_end()
    267 
    268         if self._exclusive_fp and self._close_exclusive_fp_after_loading:

~/.virtualenvs/venv/lib64/python3.8/site-packages/PIL/PngImagePlugin.py in load_end(self)
    947             except AttributeError:
    948                 logger.debug("%r %s %s (unknown)", cid, pos, length)
--> 949                 s = ImageFile._safe_read(self.fp, length)
    950                 if cid[1:2].islower():
    951                     self.private_chunks.append((cid, s, True))

~/.virtualenvs/venv/lib64/python3.8/site-packages/PIL/ImageFile.py in _safe_read(fp, size)
    566         size -= len(block)
    567     if sum(len(d) for d in data) < size:
--> 568         raise OSError("Truncated File Read")
    569     return b"".join(data)
    570 

OSError: Truncated File Read
```

### What are your OS, Python and Pillow versions?

* OS: Linux 5.11.17-300.fc34.x86_64 #1 SMP Wed Apr 28 14:21:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
* Python: 3.8.9
* Pillow: 8.2.0

```python
import PIL
image_path = 'error_image.png'

img = PIL.Image.open(image_path)
img.load()
print(f"Loaded {PIL.ImageFile.LOAD_TRUNCATED_IMAGES=}")

PIL.ImageFile.LOAD_TRUNCATED_IMAGES = True

img = PIL.Image.open(image_path)
img.load()
print(f"Loaded {PIL.ImageFile.LOAD_TRUNCATED_IMAGES=}")
```

![error_image](https://user-images.githubusercontent.com/28957750/118103782-9640be80-b3da-11eb-9d46-4adac6e33ecf.png)
![gimp](https://user-images.githubusercontent.com/28957750/118103790-980a8200-b3da-11eb-829c-e68a04932ceb.png)




Discussion:
The problem is that the png apparently has a "I" chunk, which is invalid, as chunk IDs are 4 characters. 

Resolving patch:
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -920,6 +920,8 @@ def load_read(self, read_bytes):
 
     def load_end(self):
         """internal: finished reading image data"""
+        if self.__idat != 0:
+            self.fp.read(self.__idat)
         while True:
             self.fp.read(4)  # CRC
 


Test patch:
diff --git a/Tests/images/padded_idat.png b/Tests/images/padded_idat.png
new file mode 100644
Binary files /dev/null and b/Tests/images/padded_idat.png differ
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -628,6 +628,23 @@ def test_textual_chunks_after_idat(self):
         with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
             assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
 
+    def test_padded_idat(self):
+        # This image has been manually hexedited
+        # so that the IDAT chunk has padding at the end
+        # Set MAXBLOCK to the length of the actual data
+        # so that the decoder finishes reading before the chunk ends
+        MAXBLOCK = ImageFile.MAXBLOCK
+        ImageFile.MAXBLOCK = 45
+        ImageFile.LOAD_TRUNCATED_IMAGES = True
+
+        with Image.open("Tests/images/padded_idat.png") as im:
+            im.load()
+
+            ImageFile.MAXBLOCK = MAXBLOCK
+            ImageFile.LOAD_TRUNCATED_IMAGES = False
+
+            assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
+
     def test_specify_bits(self, tmp_path):
         im = hopper("P")