forked from python-pillow/Pillow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_file_pdf.py
323 lines (249 loc) · 9.18 KB
/
test_file_pdf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
import io
import os
import os.path
import tempfile
import time
import pytest
from PIL import Image, PdfParser
from .helper import hopper, mark_if_feature_version
def helper_save_as_pdf(tmp_path, mode, **kwargs):
# Arrange
im = hopper(mode)
outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
# Act
im.save(outfile, **kwargs)
# Assert
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
with PdfParser.PdfParser(outfile) as pdf:
if kwargs.get("append_images", False) or kwargs.get("append", False):
assert len(pdf.pages) > 1
else:
assert len(pdf.pages) > 0
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert im.size == size
return outfile
def test_monochrome(tmp_path):
# Arrange
mode = "1"
# Act / Assert
outfile = helper_save_as_pdf(tmp_path, mode)
assert os.path.getsize(outfile) < 15000
def test_greyscale(tmp_path):
# Arrange
mode = "L"
# Act / Assert
helper_save_as_pdf(tmp_path, mode)
def test_rgb(tmp_path):
# Arrange
mode = "RGB"
# Act / Assert
helper_save_as_pdf(tmp_path, mode)
def test_p_mode(tmp_path):
# Arrange
mode = "P"
# Act / Assert
helper_save_as_pdf(tmp_path, mode)
def test_cmyk_mode(tmp_path):
# Arrange
mode = "CMYK"
# Act / Assert
helper_save_as_pdf(tmp_path, mode)
def test_unsupported_mode(tmp_path):
im = hopper("LA")
outfile = str(tmp_path / "temp_LA.pdf")
with pytest.raises(ValueError):
im.save(outfile)
def test_resolution(tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, resolution=150)
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (61.44, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (61.44, 61.44)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_save_all(tmp_path):
# Single frame image
helper_save_as_pdf(tmp_path, "RGB", save_all=True)
# Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, save_all=True)
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
# Append images
ims = [hopper()]
im.copy().save(outfile, save_all=True, append_images=ims)
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
# Test appending using a generator
def im_generator(ims):
yield from ims
im.save(outfile, save_all=True, append_images=im_generator(ims))
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
# Append JPEG images
with Image.open("Tests/images/flower.jpg") as jpeg:
jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
def test_multiframe_normal_save(tmp_path):
# Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile)
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
def test_pdf_open(tmp_path):
# fail on a buffer full of null bytes
with pytest.raises(PdfParser.PdfFormatError):
PdfParser.PdfParser(buf=bytearray(65536))
# make an empty PDF object
with PdfParser.PdfParser() as empty_pdf:
assert len(empty_pdf.pages) == 0
assert len(empty_pdf.info) == 0
assert not empty_pdf.should_close_buf
assert not empty_pdf.should_close_file
# make a PDF file
pdf_filename = helper_save_as_pdf(tmp_path, "RGB")
# open the PDF file
with PdfParser.PdfParser(filename=pdf_filename) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert hopper_pdf.should_close_buf
assert hopper_pdf.should_close_file
# read a PDF file from a buffer with a non-zero offset
with open(pdf_filename, "rb") as f:
content = b"xyzzy" + f.read()
with PdfParser.PdfParser(buf=content, start_offset=5) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert not hopper_pdf.should_close_buf
assert not hopper_pdf.should_close_file
# read a PDF file from an already open file
with open(pdf_filename, "rb") as f:
with PdfParser.PdfParser(f=f) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert hopper_pdf.should_close_buf
assert not hopper_pdf.should_close_file
def test_pdf_append_fails_on_nonexistent_file():
im = hopper("RGB")
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(OSError):
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
def check_pdf_pages_consistency(pdf):
pages_info = pdf.read_indirect(pdf.pages_ref)
assert b"Parent" not in pages_info
assert b"Kids" in pages_info
kids_not_used = pages_info[b"Kids"]
for page_ref in pdf.pages:
while True:
if page_ref in kids_not_used:
kids_not_used.remove(page_ref)
page_info = pdf.read_indirect(page_ref)
assert b"Parent" in page_info
page_ref = page_info[b"Parent"]
if page_ref == pdf.pages_ref:
break
assert pdf.pages_ref == page_info[b"Parent"]
assert kids_not_used == []
def test_pdf_append(tmp_path):
# make a PDF file
pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser")
# open it, check pages and info
with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf:
assert len(pdf.pages) == 1
assert len(pdf.info) == 4
assert pdf.info.Title == os.path.splitext(os.path.basename(pdf_filename))[0]
assert pdf.info.Producer == "PdfParser"
assert b"CreationDate" in pdf.info
assert b"ModDate" in pdf.info
check_pdf_pages_consistency(pdf)
# append some info
pdf.info.Title = "abc"
pdf.info.Author = "def"
pdf.info.Subject = "ghi\uABCD"
pdf.info.Keywords = "qw)e\\r(ty"
pdf.info.Creator = "hopper()"
pdf.start_writing()
pdf.write_xref_and_trailer()
# open it again, check pages and info again
with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.pages) == 1
assert len(pdf.info) == 8
assert pdf.info.Title == "abc"
assert b"CreationDate" in pdf.info
assert b"ModDate" in pdf.info
check_pdf_pages_consistency(pdf)
# append two images
mode_cmyk = hopper("CMYK")
mode_p = hopper("P")
mode_cmyk.save(pdf_filename, append=True, save_all=True, append_images=[mode_p])
# open the PDF again, check pages and info again
with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.pages) == 3
assert len(pdf.info) == 8
assert PdfParser.decode_text(pdf.info[b"Title"]) == "abc"
assert pdf.info.Title == "abc"
assert pdf.info.Producer == "PdfParser"
assert pdf.info.Keywords == "qw)e\\r(ty"
assert pdf.info.Subject == "ghi\uABCD"
assert b"CreationDate" in pdf.info
assert b"ModDate" in pdf.info
check_pdf_pages_consistency(pdf)
def test_pdf_info(tmp_path):
# make a PDF file
pdf_filename = helper_save_as_pdf(
tmp_path,
"RGB",
title="title",
author="author",
subject="subject",
keywords="keywords",
creator="creator",
producer="producer",
creationDate=time.strptime("2000", "%Y"),
modDate=time.strptime("2001", "%Y"),
)
# open it, check pages and info
with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.info) == 8
assert pdf.info.Title == "title"
assert pdf.info.Author == "author"
assert pdf.info.Subject == "subject"
assert pdf.info.Keywords == "keywords"
assert pdf.info.Creator == "creator"
assert pdf.info.Producer == "producer"
assert pdf.info.CreationDate == time.strptime("2000", "%Y")
assert pdf.info.ModDate == time.strptime("2001", "%Y")
check_pdf_pages_consistency(pdf)
def test_pdf_append_to_bytesio():
im = hopper("RGB")
f = io.BytesIO()
im.save(f, format="PDF")
initial_size = len(f.getvalue())
assert initial_size > 0
im = hopper("P")
f = io.BytesIO(f.getvalue())
im.save(f, format="PDF", append=True)
assert len(f.getvalue()) > initial_size
@pytest.mark.timeout(1)
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline):
malicious = b" trailer<<>>" + newline * 3456
# This particular exception isn't relevant here.
# The important thing is it doesn't timeout, cause a ReDoS (CVE-2021-25292).
with pytest.raises(PdfParser.PdfFormatError):
PdfParser.PdfParser(buf=malicious)