webp: handle ImageFile.LOAD_TRUNCATED_IMAGES

When set, don't raise an error on truncated content.
This commit is contained in:
Benoit Pierre 2015-03-10 22:27:00 +01:00
parent b4fcc44eb9
commit 3b736c5a0f
2 changed files with 55 additions and 22 deletions

View File

@ -7,6 +7,11 @@ _VALID_WEBP_MODES = ("RGB", "RGBA")
_VP8_MODES_BY_IDENTIFIER = (b"VP8 ", b"VP8X", b"VP8L") _VP8_MODES_BY_IDENTIFIER = (b"VP8 ", b"VP8X", b"VP8L")
_INFO_CHUNKS = {
b'EXIF': 'exif',
b'ICCP': 'icc_profile',
}
def _accept(prefix): def _accept(prefix):
is_riff_file_format = prefix[:4] == b"RIFF" is_riff_file_format = prefix[:4] == b"RIFF"
@ -96,9 +101,8 @@ class WebPImageFile(ImageFile.ImageFile):
while True: while True:
chunk_header = self.fp.read(8) chunk_header = self.fp.read(8)
if 8 != len(chunk_header): if len(chunk_header) < 8:
if first_chunk: # EOF.
raise SyntaxError("not a WebP file")
break break
chunk_fourcc = chunk_header[0:4] chunk_fourcc = chunk_header[0:4]
@ -125,26 +129,19 @@ class WebPImageFile(ImageFile.ImageFile):
elif b'ALPH' == chunk_fourcc: elif b'ALPH' == chunk_fourcc:
mode = 'RGBA' mode = 'RGBA'
elif b'EXIF' == chunk_fourcc: elif chunk_fourcc in _INFO_CHUNKS:
exif = self.fp.read(chunk_size) data = self.fp.read(chunk_size)
if chunk_size != len(exif): if len(data) < chunk_size:
raise SyntaxError("bad EXIF chunk") if ImageFile.LOAD_TRUNCATED_IMAGES:
self.info["exif"] = exif # Simulate EOF.
break
msg = "image file is truncated: incomplete %s chunk" % chunk_fourcc
raise IOError(msg)
self.info[_INFO_CHUNKS[chunk_fourcc]] = data
chunk_size = 0 chunk_size = 0
elif b'ICCP' == chunk_fourcc: # Skip to next chunk.
icc_profile = self.fp.read(chunk_size) self.fp.seek(chunk_size, os.SEEK_CUR)
if chunk_size != len(icc_profile):
raise SyntaxError("bad ICCP chunk")
self.info["icc_profile"] = icc_profile
chunk_size = 0
if chunk_size > 0:
# Skip to next chunk.
pos = self.fp.tell()
self.fp.seek(chunk_size, os.SEEK_CUR)
if self.fp.tell() != (pos + chunk_size):
raise SyntaxError("not a WebP file")
first_chunk = False first_chunk = False

View File

@ -1,6 +1,8 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image, ImageFile
from io import BytesIO
try: try:
from PIL import _webp from PIL import _webp
@ -72,6 +74,40 @@ class TestFileWebp(PillowTestCase):
target = hopper("RGB") target = hopper("RGB")
self.assert_image_similar(image, target, 12) self.assert_image_similar(image, target, 12)
def test_truncated(self):
assert False == ImageFile.LOAD_TRUNCATED_IMAGES
with open('Tests/images/flower.webp', 'rb') as fp:
full_data = fp.read()
# Truncate in the middle (VP8 chunk).
half_data = full_data[:len(full_data)//2]
im = Image.open(BytesIO(half_data))
self.assertRaises(IOError, im.load)
im = Image.open(BytesIO(half_data))
ImageFile.LOAD_TRUNCATED_IMAGES = True
try:
im.load()
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
original = Image.open(BytesIO(full_data))
width, height = original.size
# Check we decoded at least part of the image (top).
top_area = (0, 0, width-1, 31)
self.assert_image_equal(im.crop(top_area), original.crop(top_area))
# Bottom should be blank.
bottom_area = (width-33, height-33, width-1, height-1)
self.assert_image_equal(im.crop(bottom_area), Image.new(original.mode, (32, 32)))
# Truncate at the end (EXIF chunk).
most_data = full_data[:-4*1024]
self.assertRaises(IOError, Image.open, BytesIO(most_data))
ImageFile.LOAD_TRUNCATED_IMAGES = True
try:
im = Image.open(BytesIO(most_data))
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
im.load()
# Check we decoded the whole image.
self.assert_image_equal(im, original)
def test_info_compression(self): def test_info_compression(self):
for name, compression in ( for name, compression in (
('flower2' , 'lossy'), ('flower2' , 'lossy'),