Tolerate non-dictionary /Resources and /XObject in image scanner
A malformed PDF may store a non-dictionary object (an array, name, or other type) at /Resources or /Resources /XObject. The pdfinfo image scanner iterated these with .items()/.as_dict() and probed them with the `in` operator, which raise TypeError/ValueError on non-dictionary pikepdf objects and crashed PdfInfo on otherwise-processable files. OCRmyPDF's domain is messy, machine-generated PDFs, so scanning must tolerate this. Guard _image_xobjects and _find_form_xobject_images with isinstance(x, Dictionary) before iterating, treating a non-dictionary /Resources or /XObject as "no image XObjects". This is the same robustness class as the pdfa.py find_nonembedded_cid_fonts fix, applied to the pdfinfo image scanner.
This commit is contained in:
@@ -287,9 +287,15 @@ def _image_xobjects(container) -> Iterator[tuple[Object, str]]:
|
|||||||
if Name.Resources not in container:
|
if Name.Resources not in container:
|
||||||
return
|
return
|
||||||
resources = container[Name.Resources]
|
resources = container[Name.Resources]
|
||||||
if Name.XObject not in resources:
|
# A malformed PDF may store a non-dictionary at /Resources or
|
||||||
|
# /Resources /XObject; treat that as "no image XObjects" instead of
|
||||||
|
# crashing when we try to iterate it.
|
||||||
|
if not isinstance(resources, Dictionary):
|
||||||
return
|
return
|
||||||
for key, candidate in resources[Name.XObject].items():
|
xobjects = resources.get(Name.XObject)
|
||||||
|
if not isinstance(xobjects, Dictionary):
|
||||||
|
return
|
||||||
|
for key, candidate in xobjects.items():
|
||||||
if candidate is None or Name.Subtype not in candidate:
|
if candidate is None or Name.Subtype not in candidate:
|
||||||
continue
|
continue
|
||||||
if candidate[Name.Subtype] == Name.Image:
|
if candidate[Name.Subtype] == Name.Image:
|
||||||
@@ -336,9 +342,14 @@ def _find_form_xobject_images(pdf: Pdf, container: Object, contentsinfo: Content
|
|||||||
if Name.Resources not in container:
|
if Name.Resources not in container:
|
||||||
return
|
return
|
||||||
resources = container[Name.Resources]
|
resources = container[Name.Resources]
|
||||||
if Name.XObject not in resources:
|
# As in _image_xobjects, tolerate a non-dictionary /Resources or
|
||||||
|
# /Resources /XObject in a malformed PDF rather than crashing.
|
||||||
|
if not isinstance(resources, Dictionary):
|
||||||
return
|
return
|
||||||
xobjs = resources[Name.XObject].as_dict()
|
xobject = resources.get(Name.XObject)
|
||||||
|
if not isinstance(xobject, Dictionary):
|
||||||
|
return
|
||||||
|
xobjs = xobject.as_dict()
|
||||||
for xobj in xobjs:
|
for xobj in xobjs:
|
||||||
candidate = xobjs[xobj]
|
candidate = xobjs[xobj]
|
||||||
if candidate is None or candidate.get(Name.Subtype) != Name.Form:
|
if candidate is None or candidate.get(Name.Subtype) != Name.Form:
|
||||||
|
|||||||
@@ -448,6 +448,55 @@ def test_fill_ink_cs_resets_color_to_black():
|
|||||||
assert _ink_of_first_xobject(b"0.8 0.2 0.2 rg /DeviceGray cs /Im0 Do") is Ink.mono
|
assert _ink_of_first_xobject(b"0.8 0.2 0.2 rg /DeviceGray cs /Im0 Do") is Ink.mono
|
||||||
|
|
||||||
|
|
||||||
|
def test_nondict_xobject_tolerated(outdir):
|
||||||
|
# A malformed PDF may store a non-dictionary object (here an Array) at
|
||||||
|
# /Resources /XObject. Scanning for images must tolerate this rather than
|
||||||
|
# crash on .items(); OCRmyPDF's domain is messy machine-generated PDFs.
|
||||||
|
# Same robustness class as the pdfa.py find_nonembedded_cid_fonts fix.
|
||||||
|
pdf = pikepdf.Pdf.new()
|
||||||
|
page = pdf.add_blank_page(page_size=(612, 792))
|
||||||
|
page.Resources = pikepdf.Dictionary(
|
||||||
|
Font=pikepdf.Array([]), XObject=pikepdf.Array([])
|
||||||
|
)
|
||||||
|
out = outdir / 'malformed_xobj.pdf'
|
||||||
|
pdf.save(out)
|
||||||
|
|
||||||
|
info = pdfinfo.PdfInfo(out)
|
||||||
|
assert len(info) == 1
|
||||||
|
assert len(info[0].images) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'resources',
|
||||||
|
[
|
||||||
|
pikepdf.Array([]), # non-dict /Resources
|
||||||
|
pikepdf.Name.Foo, # non-dict /Resources (name)
|
||||||
|
pikepdf.Dictionary(XObject=pikepdf.Array([])), # non-dict /XObject
|
||||||
|
pikepdf.Dictionary(XObject=pikepdf.Name.Foo), # non-dict /XObject (name)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_image_scanners_tolerate_nondict_resources(resources):
|
||||||
|
# Exercise the image scanners directly on an in-memory container whose
|
||||||
|
# /Resources or /Resources /XObject is not a dictionary. (pikepdf
|
||||||
|
# normalizes a non-dict /Resources assigned to a page on save, so these
|
||||||
|
# cases must be built in memory to reach the scanner unmodified.)
|
||||||
|
from ocrmypdf.pdfinfo._contentstream import ContentsInfo
|
||||||
|
from ocrmypdf.pdfinfo._image import _find_form_xobject_images, _image_xobjects
|
||||||
|
|
||||||
|
container = pikepdf.Dictionary(Type=pikepdf.Name.Page, Resources=resources)
|
||||||
|
empty = ContentsInfo(
|
||||||
|
xobject_settings=[],
|
||||||
|
inline_images=[],
|
||||||
|
found_vector=False,
|
||||||
|
found_text=False,
|
||||||
|
name_index={},
|
||||||
|
)
|
||||||
|
pdf = pikepdf.Pdf.new()
|
||||||
|
|
||||||
|
assert list(_image_xobjects(container)) == []
|
||||||
|
assert list(_find_form_xobject_images(pdf, container, empty)) == []
|
||||||
|
|
||||||
|
|
||||||
def test_imageinfo_ink_inherited_in_form_xobject(outdir):
|
def test_imageinfo_ink_inherited_in_form_xobject(outdir):
|
||||||
# A mask drawn inside a Form XObject inherits the fill color set before the
|
# A mask drawn inside a Form XObject inherits the fill color set before the
|
||||||
# Do that paints the form; the gray classification must reach the mask.
|
# Do that paints the form; the gray classification must reach the mask.
|
||||||
|
|||||||
Reference in New Issue
Block a user