Skip to content

Commit

Permalink
BUG: Avoid exceeding recursion depth when retrieving image mode (#2251)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan6419846 authored Oct 10, 2023
1 parent 5c3550f commit c6908ea
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 3 deletions.
13 changes: 10 additions & 3 deletions pypdf/_xobj_image_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,24 @@
"", "1", "RGB", "2bits", "4bits", "P", "L", "RGBA", "CMYK"
]

MAX_IMAGE_MODE_NESTING_DEPTH: int = 10


def _get_imagemode(
color_space: Union[str, List[Any], Any],
color_components: int,
prev_mode: mode_str_type,
depth: int = 0,
) -> Tuple[mode_str_type, bool]:
"""
Returns
Image mode not taking into account mask(transparency)
ColorInversion is required (like for some DeviceCMYK)
"""
if depth > MAX_IMAGE_MODE_NESTING_DEPTH:
raise PdfReadError(
"Color spaces nested too deep. If required, consider increasing MAX_IMAGE_MODE_NESTING_DEPTH."
)
if isinstance(color_space, NullObject):
return "", False
if isinstance(color_space, str):
Expand All @@ -63,22 +70,22 @@ def _get_imagemode(
color_space = color_space[1]
if isinstance(color_space, IndirectObject):
color_space = color_space.get_object()
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode)
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode, depth + 1)
if mode2 in ("RGB", "CMYK"):
mode2 = "P"
return mode2, invert_color
elif color_space[0] == "/Separation":
color_space = color_space[2]
if isinstance(color_space, IndirectObject):
color_space = color_space.get_object()
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode)
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode, depth + 1)
return mode2, True
elif color_space[0] == "/DeviceN":
color_components = len(color_space[1])
color_space = color_space[2]
if isinstance(color_space, IndirectObject): # pragma: no cover
color_space = color_space.get_object()
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode)
mode2, invert_color = _get_imagemode(color_space, color_components, prev_mode, depth + 1)
return mode2, invert_color

mode_map = {
Expand Down
27 changes: 27 additions & 0 deletions tests/test_xobject_image_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Test the pypdf._xobj_image_helpers module."""
from io import BytesIO

import pytest

from pypdf import PdfReader
from pypdf.errors import PdfReadError

from . import get_data_from_url


@pytest.mark.enable_socket()
def test_get_imagemode_recursion_depth():
"""Avoid infinite recursion for nested color spaces."""
url = "https://github.com/py-pdf/pypdf/files/12814018/out1.pdf"
name = "issue2240.pdf"
# Simple example: Just let the color space object reference itself.
# The alternative would be to generate a chain of referencing objects.
content = get_data_from_url(url, name=name)
source = b"\n10 0 obj\n[ /DeviceN [ /HKS#2044#20K /Magenta /Yellow /Black ] 7 0 R 11 0 R 12 0 R ]\nendobj\n"
target = b"\n10 0 obj\n[ /DeviceN [ /HKS#2044#20K /Magenta /Yellow /Black ] 10 0 R 11 0 R 12 0 R ]\nendobj\n"
reader = PdfReader(BytesIO(content.replace(source, target)))
with pytest.raises(
PdfReadError,
match="Color spaces nested too deep. If required, consider increasing MAX_IMAGE_MODE_NESTING_DEPTH."
):
reader.pages[0].images[0]

0 comments on commit c6908ea

Please sign in to comment.