Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pypdf/_configuration.py: 84%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Configuration value handling."""
2import dataclasses
3import functools
4import shutil
5from collections.abc import Iterator
6from contextlib import contextmanager
7from contextvars import ContextVar
8from typing import Any, Literal, Optional, Union
11@functools.cache
12def _determine_jbig2dec_binary() -> Optional[str]:
13 """
14 Small utility function to determine the `jbig2dec` binary path only once.
16 Returns:
17 The corresponding binary path if available.
18 """
19 return shutil.which("jbig2dec")
22@dataclasses.dataclass(frozen=True)
23class Configuration:
24 """
25 Configuration used while processing PDF files.
27 These limits mostly protect against excessive resource consumption caused by malformed or malicious PDF files.
29 .. note::
31 Avoid relying on specific implementation details of this class. The main public API consists of the values
32 being considered read-only for each instance with the overwrite method to create a new instance with the
33 specified values changed.
34 """
36 maximum_declared_stream_length: int = 75_000_000
37 """The maximum allowed declared ``/Length`` value for streams."""
38 array_based_stream_maximum_output_length: int = 75_000_000
39 """The maximum allowed output length for array-based streams."""
40 array_maximum_element_count: int = 100_000
41 """The maximum allowed number of elements inside an array."""
42 dictionary_maximum_element_count: int = 100_000
43 """The maximum allowed number of key-value pairs inside a dictionary."""
45 jbig2_maximum_output_length: int = 75_000_000
46 """
47 The maximum allowed number of uncompressed bytes during decompression when using the ``/JBIG2Decode`` filter
48 (JBIG2 images).
49 """
50 lzw_maximum_output_length: int = 75_000_000
51 """
52 The maximum allowed number of uncompressed bytes during decompression when using the ``/LZWDecode`` filter
53 (LZW compression).
54 """
55 run_length_maximum_output_length: int = 75_000_000
56 """
57 The maximum allowed number of uncompressed bytes during decompression when using the ``/RunLengthDecode`` filter
58 (run-length compression).
59 """
60 zlib_maximum_output_length: int = 75_000_000
61 """
62 The maximum allowed number of uncompressed bytes during decompression when using the ``/FlateDecode`` filter
63 (zlib compression).
64 """
65 zlib_maximum_recovery_input_length: int = 5_000_000
66 """
67 The maximum allowed number of bytes to attempt the recovery with when using the ``/FlateDecode`` filter
68 (zlib compression).
69 """
70 brotli_maximum_output_length: int = 75_000_000
71 """
72 The maximum allowed number of uncompressed bytes during decompression when using the ``/BrotliDecode`` filter
73 (Brotli compression).
74 """
76 flate_maximum_columns: int = 250_000
77 """The maximum allowed number of columns when using the ``/FlateDecode`` filter."""
78 flate_maximum_row_length: int = 4_000_000
79 """The maximum allowed row length when using the ``/FlateDecode`` filter."""
81 image_maximum_buffer_size: int = 75_000_000
82 """The maximum allowed number of bytes to allocate for images."""
84 stream_filters_maximum_length: int = 16
85 """The maximum allowed number of filters per stream."""
86 stream_decoding_work_maximum_length: int = 300_000_000
87 """The maximum allowed accumulated decoding work in bytes per stream."""
88 stream_parsing_work_maximum_count: int = 2_000_000
89 """
90 The maximum parsing budget for a content stream, including operands, operators, and elements in
91 nested arrays and dictionaries.
92 """
94 xmp_maximum_input_length: int = 5_000_000
95 """The maximum allowed actual decompressed stream length in bytes for XMP data."""
96 xmp_maximum_element_count: int = 100_000
97 """The maximum allowed number of elements for XMP data."""
99 outline_maximum_entries: int = 100_000
100 """The maximum allowed number of outline entries."""
101 outline_maximum_depth: int = 100
102 """The maximum allowed depth for outline entries."""
104 page_tree_maximum_entries: int = 100_000
105 """The maximum allowed number of entries in the page tree."""
106 page_tree_maximum_depth: int = 100
107 """The maximum allowed depth of the page tree."""
109 form_fields_maximum_entries: int = 100_000
110 """The maximum allowed number of form fields."""
111 form_fields_maximum_depth: int = 100
112 """The maximum allowed depth for form fields."""
114 xform_maximum_invocations_per_extraction: int = 5_000
115 """The maximum allowed number of ``/XObject`` forms per page during text extraction."""
117 jbig2dec_binary: Optional[str] = dataclasses.field(default_factory=_determine_jbig2dec_binary)
118 """
119 The location of the ``jbig2dec`` binary.
121 This either is a string holding the path to the binary, or ``None`` if the binary could not be found
122 or the binary should not be called.
123 """
125 page_merge_box: Literal["cropbox", "trimbox"] = "cropbox" # pypdf <= 3.4.0 used "trimbox"
126 """The page box to use during merge."""
128 disable_legacy_handling: bool = False
129 """
130 Temporary configuration value for applications which do not rely on old overwrites and thus
131 are able to skip legacy handling.
133 This disables looking for changes of the old constants during each initialization of the reader
134 class. Please note that while this reduces the required overhead, it will disable all related
135 deprecation warnings/errors. Setting this flag while legacy constants are intentionally being
136 modified is unsupported/incorrect.
138 After the deprecation period, this configuration value will be removed with its own deprecation
139 period as well.
140 """
142 def with_overwrites(self, **kwargs: Any) -> "Configuration":
143 """
144 Creates a new configuration with the specified values overwritten.
146 Args:
147 **kwargs: Configuration field names and their new values.
149 Returns:
150 A new configuration with the specified values overwritten.
151 """
152 return dataclasses.replace(self, **kwargs)
155# A default configuration without any value changes.
156DEFAULT_CONFIGURATION = Configuration()
158# The current configuration.
159CURRENT_CONFIGURATION: ContextVar[Configuration] = ContextVar(
160 "pypdf_configuration",
161 default=DEFAULT_CONFIGURATION,
162)
165def get_configuration() -> Configuration:
166 """
167 Get the current configuration.
169 Returns:
170 The current configuration.
171 """
172 return CURRENT_CONFIGURATION.get()
175def _build_configuration(
176 configuration: Optional[Configuration],
177 overwrites: dict[str, Any],
178) -> Configuration:
179 """
180 Utility function to build a new configuration from the given values.
182 Args:
183 configuration: A possible explicit configuration to use instead of the current configuration.
184 overwrites: Entries to overwrite.
186 Returns:
187 The generated configuration.
188 """
189 base = configuration if configuration is not None else get_configuration()
190 return base.with_overwrites(**overwrites)
193def overwrite_configuration(
194 configuration: Optional[Configuration] = None,
195 **overwrites: Any,
196) -> Configuration:
197 """
198 Overwrite the configuration for the current execution context.
200 Args:
201 configuration: A direct configuration instance to use.
202 **overwrites: Configuration parameters to overwrite in the current or given configuration.
204 Returns:
205 The new configuration.
206 """
207 new_configuration = _build_configuration(configuration, overwrites)
208 CURRENT_CONFIGURATION.set(new_configuration)
209 return new_configuration
212@contextmanager
213def apply_configuration(
214 configuration: Optional[Configuration] = None,
215 **overwrites: Any,
216) -> Iterator[Configuration]:
217 """
218 Temporarily overwrite the configuration.
220 Args:
221 configuration: A direct configuration instance to use.
222 **overwrites: Configuration parameters to overwrite in the current or given configuration.
224 Returns:
225 The new configuration.
226 """
227 new_configuration = _build_configuration(configuration, overwrites)
228 token = CURRENT_CONFIGURATION.set(new_configuration)
229 try:
230 yield new_configuration
231 finally:
232 CURRENT_CONFIGURATION.reset(token)
235# Map the new field names to their legacy names.
236LEGACY_NAME_MAPPING = {
237 "maximum_declared_stream_length": "MAX_DECLARED_STREAM_LENGTH",
238 "array_based_stream_maximum_output_length": "MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH",
239 "jbig2_maximum_output_length": "JBIG2_MAX_OUTPUT_LENGTH",
240 "lzw_maximum_output_length": "LZW_MAX_OUTPUT_LENGTH",
241 "run_length_maximum_output_length": "RUN_LENGTH_MAX_OUTPUT_LENGTH",
242 "zlib_maximum_output_length": "ZLIB_MAX_OUTPUT_LENGTH",
243 "zlib_maximum_recovery_input_length": "ZLIB_MAX_RECOVERY_INPUT_LENGTH",
244 "flate_maximum_columns": "FLATE_MAX_COLUMNS",
245 "flate_maximum_row_length": "FLATE_MAX_ROW_LENGTH",
246 "image_maximum_buffer_size": "FLATE_MAX_BUFFER_SIZE",
247 "xmp_maximum_input_length": "XMP_MAX_INPUT_LENGTH",
248 "xmp_maximum_element_count": "XMP_MAX_ELEMENT_COUNT",
249 "jbig2dec_binary": "JBIG2DEC_BINARY",
250 "page_merge_box": "MERGE_CROP_BOX",
251}
253# Record which legacy overwrites have already emitted a warning in the current process/session.
254WARNED_LEGACY_OVERWRITES: set[str] = set()
257def apply_legacy_configuration() -> Configuration:
258 """
259 Overwrite the current configuration with the legacy configuration if it has been changed.
261 Returns:
262 The new configuration which has been applied.
263 """
264 configuration = get_configuration()
265 if configuration.disable_legacy_handling:
266 return configuration
268 from pypdf._page import MERGE_CROP_BOX # noqa: PLC0415
269 from pypdf.filters import ( # noqa: PLC0415
270 FLATE_MAX_BUFFER_SIZE,
271 FLATE_MAX_COLUMNS,
272 FLATE_MAX_ROW_LENGTH,
273 JBIG2_MAX_OUTPUT_LENGTH,
274 JBIG2DEC_BINARY,
275 LZW_MAX_OUTPUT_LENGTH,
276 MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH,
277 MAX_DECLARED_STREAM_LENGTH,
278 RUN_LENGTH_MAX_OUTPUT_LENGTH,
279 ZLIB_MAX_OUTPUT_LENGTH,
280 ZLIB_MAX_RECOVERY_INPUT_LENGTH,
281 )
282 from pypdf.xmp import XMP_MAX_ELEMENT_COUNT, XMP_MAX_INPUT_LENGTH # noqa: PLC0415
284 legacy_values = {
285 "MAX_DECLARED_STREAM_LENGTH": MAX_DECLARED_STREAM_LENGTH,
286 "MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH": MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH,
287 "JBIG2_MAX_OUTPUT_LENGTH": JBIG2_MAX_OUTPUT_LENGTH,
288 "LZW_MAX_OUTPUT_LENGTH": LZW_MAX_OUTPUT_LENGTH,
289 "RUN_LENGTH_MAX_OUTPUT_LENGTH": RUN_LENGTH_MAX_OUTPUT_LENGTH,
290 "ZLIB_MAX_OUTPUT_LENGTH": ZLIB_MAX_OUTPUT_LENGTH,
291 "ZLIB_MAX_RECOVERY_INPUT_LENGTH": ZLIB_MAX_RECOVERY_INPUT_LENGTH,
292 "FLATE_MAX_COLUMNS": FLATE_MAX_COLUMNS,
293 "FLATE_MAX_ROW_LENGTH": FLATE_MAX_ROW_LENGTH,
294 "FLATE_MAX_BUFFER_SIZE": FLATE_MAX_BUFFER_SIZE,
295 "XMP_MAX_INPUT_LENGTH": XMP_MAX_INPUT_LENGTH,
296 "XMP_MAX_ELEMENT_COUNT": XMP_MAX_ELEMENT_COUNT,
297 "JBIG2DEC_BINARY": JBIG2DEC_BINARY,
298 "MERGE_CROP_BOX": MERGE_CROP_BOX,
299 }
300 overwrites: dict[str, Union[int, str, None]] = {}
302 for field_name, legacy_name in LEGACY_NAME_MAPPING.items():
303 legacy_value = legacy_values[legacy_name]
305 if legacy_value != getattr(DEFAULT_CONFIGURATION, field_name):
306 if legacy_name not in WARNED_LEGACY_OVERWRITES:
307 from pypdf._utils import deprecate_with_replacement # noqa: PLC0415
309 deprecate_with_replacement(
310 old_name=legacy_name,
311 new_name=f"Configuration.{field_name}",
312 removed_in="7.0.0",
313 )
314 WARNED_LEGACY_OVERWRITES.add(legacy_name)
316 overwrites[field_name] = legacy_value
318 if not overwrites:
319 return configuration
320 return overwrite_configuration(**overwrites) # type: ignore[arg-type]