mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-13 10:46:16 +03:00
Merge pull request #5121 from Piolie/PPMheaders
Improve handling of PPM headers
This commit is contained in:
commit
6d432f2a81
|
@ -3,7 +3,7 @@ from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
||||||
|
|
||||||
|
@ -50,15 +50,70 @@ def test_pnm(tmp_path):
|
||||||
assert_image_equal_tofile(im, f)
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_magic(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"PyInvalid")
|
||||||
|
|
||||||
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_with_comments(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
|
||||||
|
|
||||||
|
with Image.open(path) as im:
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_integer_token(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\nTEST")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_too_long(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\n 01234567890")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Token too long in file header: b'01234567890'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_too_many_colors(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\n1 1\n1000\n")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Too many colors for band: 1000"
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_file(tmp_path):
|
def test_truncated_file(tmp_path):
|
||||||
path = str(tmp_path / "temp.pgm")
|
path = str(tmp_path / "temp.pgm")
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
f.write("P6")
|
f.write("P6")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError) as e:
|
||||||
with Image.open(path):
|
with Image.open(path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Reached EOF while reading header"
|
||||||
|
|
||||||
|
|
||||||
def test_neg_ppm():
|
def test_neg_ppm():
|
||||||
# Storage.c accepted negative values for xsize, ysize. the
|
# Storage.c accepted negative values for xsize, ysize. the
|
||||||
|
|
|
@ -49,26 +49,46 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
format = "PPM"
|
format = "PPM"
|
||||||
format_description = "Pbmplus image"
|
format_description = "Pbmplus image"
|
||||||
|
|
||||||
def _token(self, s=b""):
|
def _read_magic(self):
|
||||||
while True: # read until next whitespace
|
magic = b""
|
||||||
|
# read until whitespace or longest available magic number
|
||||||
|
for _ in range(6):
|
||||||
c = self.fp.read(1)
|
c = self.fp.read(1)
|
||||||
if not c or c in b_whitespace:
|
if not c or c in b_whitespace:
|
||||||
break
|
break
|
||||||
if c > b"\x79":
|
magic += c
|
||||||
raise ValueError("Expected ASCII value, found binary")
|
return magic
|
||||||
s = s + c
|
|
||||||
if len(s) > 9:
|
def _read_token(self):
|
||||||
raise ValueError("Expected int, got > 9 digits")
|
token = b""
|
||||||
return s
|
while len(token) <= 10: # read until next whitespace or limit of 10 characters
|
||||||
|
c = self.fp.read(1)
|
||||||
|
if not c:
|
||||||
|
break
|
||||||
|
elif c in b_whitespace: # token ended
|
||||||
|
if not token:
|
||||||
|
# skip whitespace at start
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
elif c == b"#":
|
||||||
|
# ignores rest of the line; stops at CR, LF or EOF
|
||||||
|
while self.fp.read(1) not in b"\r\n":
|
||||||
|
pass
|
||||||
|
continue
|
||||||
|
token += c
|
||||||
|
if not token:
|
||||||
|
# Token was not even 1 byte
|
||||||
|
raise ValueError("Reached EOF while reading header")
|
||||||
|
elif len(token) > 10:
|
||||||
|
raise ValueError(f"Token too long in file header: {token}")
|
||||||
|
return token
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
magic_number = self._read_magic()
|
||||||
# check magic
|
try:
|
||||||
s = self.fp.read(1)
|
mode = MODES[magic_number]
|
||||||
if s != b"P":
|
except KeyError:
|
||||||
raise SyntaxError("not a PPM file")
|
raise SyntaxError("not a PPM file")
|
||||||
magic_number = self._token(s)
|
|
||||||
mode = MODES[magic_number]
|
|
||||||
|
|
||||||
self.custom_mimetype = {
|
self.custom_mimetype = {
|
||||||
b"P4": "image/x-portable-bitmap",
|
b"P4": "image/x-portable-bitmap",
|
||||||
|
@ -83,29 +103,19 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
self.mode = rawmode = mode
|
self.mode = rawmode = mode
|
||||||
|
|
||||||
for ix in range(3):
|
for ix in range(3):
|
||||||
while True:
|
token = int(self._read_token())
|
||||||
while True:
|
if ix == 0: # token is the x size
|
||||||
s = self.fp.read(1)
|
xsize = token
|
||||||
if s not in b_whitespace:
|
elif ix == 1: # token is the y size
|
||||||
break
|
ysize = token
|
||||||
if s == b"":
|
|
||||||
raise ValueError("File does not extend beyond magic number")
|
|
||||||
if s != b"#":
|
|
||||||
break
|
|
||||||
s = self.fp.readline()
|
|
||||||
s = int(self._token(s))
|
|
||||||
if ix == 0:
|
|
||||||
xsize = s
|
|
||||||
elif ix == 1:
|
|
||||||
ysize = s
|
|
||||||
if mode == "1":
|
if mode == "1":
|
||||||
break
|
break
|
||||||
elif ix == 2:
|
elif ix == 2: # token is maxval
|
||||||
# maxgrey
|
maxval = token
|
||||||
if s > 255:
|
if maxval > 255:
|
||||||
if not mode == "L":
|
if not mode == "L":
|
||||||
raise ValueError(f"Too many colors for band: {s}")
|
raise ValueError(f"Too many colors for band: {token}")
|
||||||
if s < 2 ** 16:
|
if maxval < 2 ** 16:
|
||||||
self.mode = "I"
|
self.mode = "I"
|
||||||
rawmode = "I;16B"
|
rawmode = "I;16B"
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user