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

111 statements  

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 

9 

10 

11@functools.cache 

12def _determine_jbig2dec_binary() -> Optional[str]: 

13 """ 

14 Small utility function to determine the `jbig2dec` binary path only once. 

15 

16 Returns: 

17 The corresponding binary path if available. 

18 """ 

19 return shutil.which("jbig2dec") 

20 

21 

22@dataclasses.dataclass(frozen=True) 

23class Configuration: 

24 """ 

25 Configuration used while processing PDF files. 

26 

27 These limits mostly protect against excessive resource consumption caused by malformed or malicious PDF files. 

28 

29 .. note:: 

30 

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 """ 

35 

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.""" 

44 

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 """ 

75 

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.""" 

80 

81 image_maximum_buffer_size: int = 75_000_000 

82 """The maximum allowed number of bytes to allocate for images.""" 

83 

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 """ 

93 

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.""" 

98 

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.""" 

103 

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.""" 

108 

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.""" 

113 

114 xform_maximum_invocations_per_extraction: int = 5_000 

115 """The maximum allowed number of ``/XObject`` forms per page during text extraction.""" 

116 

117 jbig2dec_binary: Optional[str] = dataclasses.field(default_factory=_determine_jbig2dec_binary) 

118 """ 

119 The location of the ``jbig2dec`` binary. 

120 

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 """ 

124 

125 page_merge_box: Literal["cropbox", "trimbox"] = "cropbox" # pypdf <= 3.4.0 used "trimbox" 

126 """The page box to use during merge.""" 

127 

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. 

132 

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. 

137 

138 After the deprecation period, this configuration value will be removed with its own deprecation 

139 period as well. 

140 """ 

141 

142 def with_overwrites(self, **kwargs: Any) -> "Configuration": 

143 """ 

144 Creates a new configuration with the specified values overwritten. 

145 

146 Args: 

147 **kwargs: Configuration field names and their new values. 

148 

149 Returns: 

150 A new configuration with the specified values overwritten. 

151 """ 

152 return dataclasses.replace(self, **kwargs) 

153 

154 

155# A default configuration without any value changes. 

156DEFAULT_CONFIGURATION = Configuration() 

157 

158# The current configuration. 

159CURRENT_CONFIGURATION: ContextVar[Configuration] = ContextVar( 

160 "pypdf_configuration", 

161 default=DEFAULT_CONFIGURATION, 

162) 

163 

164 

165def get_configuration() -> Configuration: 

166 """ 

167 Get the current configuration. 

168 

169 Returns: 

170 The current configuration. 

171 """ 

172 return CURRENT_CONFIGURATION.get() 

173 

174 

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. 

181 

182 Args: 

183 configuration: A possible explicit configuration to use instead of the current configuration. 

184 overwrites: Entries to overwrite. 

185 

186 Returns: 

187 The generated configuration. 

188 """ 

189 base = configuration if configuration is not None else get_configuration() 

190 return base.with_overwrites(**overwrites) 

191 

192 

193def overwrite_configuration( 

194 configuration: Optional[Configuration] = None, 

195 **overwrites: Any, 

196) -> Configuration: 

197 """ 

198 Overwrite the configuration for the current execution context. 

199 

200 Args: 

201 configuration: A direct configuration instance to use. 

202 **overwrites: Configuration parameters to overwrite in the current or given configuration. 

203 

204 Returns: 

205 The new configuration. 

206 """ 

207 new_configuration = _build_configuration(configuration, overwrites) 

208 CURRENT_CONFIGURATION.set(new_configuration) 

209 return new_configuration 

210 

211 

212@contextmanager 

213def apply_configuration( 

214 configuration: Optional[Configuration] = None, 

215 **overwrites: Any, 

216) -> Iterator[Configuration]: 

217 """ 

218 Temporarily overwrite the configuration. 

219 

220 Args: 

221 configuration: A direct configuration instance to use. 

222 **overwrites: Configuration parameters to overwrite in the current or given configuration. 

223 

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) 

233 

234 

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} 

252 

253# Record which legacy overwrites have already emitted a warning in the current process/session. 

254WARNED_LEGACY_OVERWRITES: set[str] = set() 

255 

256 

257def apply_legacy_configuration() -> Configuration: 

258 """ 

259 Overwrite the current configuration with the legacy configuration if it has been changed. 

260 

261 Returns: 

262 The new configuration which has been applied. 

263 """ 

264 configuration = get_configuration() 

265 if configuration.disable_legacy_handling: 

266 return configuration 

267 

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 

283 

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]] = {} 

301 

302 for field_name, legacy_name in LEGACY_NAME_MAPPING.items(): 

303 legacy_value = legacy_values[legacy_name] 

304 

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 

308 

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) 

315 

316 overwrites[field_name] = legacy_value 

317 

318 if not overwrites: 

319 return configuration 

320 return overwrite_configuration(**overwrites) # type: ignore[arg-type]