_reader.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398
  1. # Copyright (c) 2006, Mathieu Fenniak
  2. # Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
  3. #
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. #
  10. # * Redistributions of source code must retain the above copyright notice,
  11. # this list of conditions and the following disclaimer.
  12. # * Redistributions in binary form must reproduce the above copyright notice,
  13. # this list of conditions and the following disclaimer in the documentation
  14. # and/or other materials provided with the distribution.
  15. # * The name of the author may not be used to endorse or promote products
  16. # derived from this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  19. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  20. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  21. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  22. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  23. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  24. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  25. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  26. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  27. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. # POSSIBILITY OF SUCH DAMAGE.
  29. import os
  30. import re
  31. import sys
  32. from collections.abc import Iterable
  33. from io import BytesIO, UnsupportedOperation
  34. from pathlib import Path
  35. from types import TracebackType
  36. from typing import (
  37. TYPE_CHECKING,
  38. Any,
  39. Callable,
  40. Optional,
  41. Union,
  42. cast,
  43. )
  44. if sys.version_info >= (3, 11):
  45. from typing import Self
  46. else:
  47. from typing_extensions import Self
  48. from ._doc_common import PdfDocCommon, convert_to_int
  49. from ._encryption import Encryption, PasswordType
  50. from ._utils import (
  51. WHITESPACES_AS_BYTES,
  52. StrByteType,
  53. StreamType,
  54. logger_warning,
  55. read_non_whitespace,
  56. read_previous_line,
  57. read_until_whitespace,
  58. skip_over_comment,
  59. skip_over_whitespace,
  60. )
  61. from .constants import TrailerKeys as TK
  62. from .errors import (
  63. EmptyFileError,
  64. FileNotDecryptedError,
  65. LimitReachedError,
  66. PdfReadError,
  67. PdfStreamError,
  68. WrongPasswordError,
  69. )
  70. from .generic import (
  71. ArrayObject,
  72. ContentStream,
  73. DecodedStreamObject,
  74. DictionaryObject,
  75. EncodedStreamObject,
  76. IndirectObject,
  77. NameObject,
  78. NullObject,
  79. NumberObject,
  80. PdfObject,
  81. StreamObject,
  82. TextStringObject,
  83. is_null_or_none,
  84. read_object,
  85. )
  86. from .xmp import XmpInformation
  87. if TYPE_CHECKING:
  88. from ._page import PageObject
  89. class PdfReader(PdfDocCommon):
  90. """
  91. Initialize a PdfReader object.
  92. This operation can take some time, as the PDF stream's cross-reference
  93. tables are read into memory.
  94. Args:
  95. stream: A File object or an object that supports the standard read
  96. and seek methods similar to a File object. Could also be a
  97. string representing a path to a PDF file.
  98. strict: Determines whether user should be warned of all
  99. problems and also causes some correctable problems to be fatal.
  100. Defaults to ``False``.
  101. password: Decrypt PDF file at initialization. If the
  102. password is None, the file will not be decrypted.
  103. Defaults to ``None``.
  104. root_object_recovery_limit: The maximum number of objects to query
  105. for recovering the Root object in non-strict mode. To disable
  106. this security measure, pass ``None``.
  107. """
  108. def __init__(
  109. self,
  110. stream: Union[StrByteType, Path],
  111. strict: bool = False,
  112. password: Union[None, str, bytes] = None,
  113. *,
  114. root_object_recovery_limit: Optional[int] = 10_000,
  115. ) -> None:
  116. self.strict = strict
  117. self.flattened_pages: Optional[list[PageObject]] = None
  118. #: Storage of parsed PDF objects.
  119. self.resolved_objects: dict[tuple[Any, Any], Optional[PdfObject]] = {}
  120. self._startxref: int = 0
  121. self.xref_index = 0
  122. self.xref: dict[int, dict[Any, Any]] = {}
  123. self.xref_free_entry: dict[int, dict[Any, Any]] = {}
  124. self.xref_objStm: dict[int, tuple[Any, Any]] = {}
  125. self.trailer = DictionaryObject()
  126. # Security parameters.
  127. self._root_object_recovery_limit = (
  128. root_object_recovery_limit if isinstance(root_object_recovery_limit, int) else sys.maxsize
  129. )
  130. # Map page indirect_reference number to page number
  131. self._page_id2num: Optional[dict[Any, Any]] = None
  132. self._validated_root: Optional[DictionaryObject] = None
  133. self._initialize_stream(stream)
  134. self._known_objects: set[tuple[int, int]] = set()
  135. self._override_encryption = False
  136. self._encryption: Optional[Encryption] = None
  137. if self.is_encrypted:
  138. self._handle_encryption(password)
  139. elif password is not None:
  140. raise PdfReadError("Not an encrypted file")
  141. def _initialize_stream(self, stream: Union[StrByteType, Path]) -> None:
  142. if hasattr(stream, "mode") and "b" not in stream.mode:
  143. logger_warning(
  144. "PdfReader stream/file object is not in binary mode. "
  145. "It may not be read correctly.",
  146. __name__,
  147. )
  148. self._stream_opened = False
  149. if isinstance(stream, (str, Path)):
  150. with open(stream, "rb") as fh:
  151. stream = BytesIO(fh.read())
  152. self._stream_opened = True
  153. self.read(stream)
  154. self.stream = stream
  155. def _handle_encryption(self, password: Optional[Union[str, bytes]]) -> None:
  156. self._override_encryption = True
  157. # Some documents may not have a /ID, use two empty
  158. # byte strings instead. Solves
  159. # https://github.com/py-pdf/pypdf/issues/608
  160. id_entry = self.trailer.get(TK.ID)
  161. id1_entry = id_entry[0].get_object().original_bytes if id_entry else b""
  162. encrypt_entry = cast(DictionaryObject, self.trailer[TK.ENCRYPT].get_object())
  163. self._encryption = Encryption.read(encrypt_entry, id1_entry)
  164. # try empty password if no password provided
  165. pwd = password if password is not None else b""
  166. if (
  167. self._encryption.verify(pwd) == PasswordType.NOT_DECRYPTED
  168. and password is not None
  169. ):
  170. # raise if password provided
  171. raise WrongPasswordError("Wrong password")
  172. self._override_encryption = False
  173. def __enter__(self) -> Self:
  174. return self
  175. def __exit__(
  176. self,
  177. exc_type: Optional[type[BaseException]],
  178. exc_val: Optional[BaseException],
  179. exc_tb: Optional[TracebackType],
  180. ) -> None:
  181. self.close()
  182. def close(self) -> None:
  183. """Close the stream if opened in __init__ and clear memory."""
  184. if self._stream_opened:
  185. self.stream.close()
  186. self.flattened_pages = []
  187. self.resolved_objects = {}
  188. self.trailer = DictionaryObject()
  189. self.xref = {}
  190. self.xref_free_entry = {}
  191. self.xref_objStm = {}
  192. @property
  193. def root_object(self) -> DictionaryObject:
  194. """Provide access to "/Root". Standardized with PdfWriter."""
  195. if self._validated_root:
  196. return self._validated_root
  197. root = self.trailer.get(TK.ROOT)
  198. if is_null_or_none(root):
  199. logger_warning('Cannot find "/Root" key in trailer', __name__)
  200. elif (
  201. cast(DictionaryObject, cast(PdfObject, root).get_object()).get("/Type")
  202. == "/Catalog"
  203. ):
  204. self._validated_root = cast(
  205. DictionaryObject, cast(PdfObject, root).get_object()
  206. )
  207. else:
  208. logger_warning("Invalid Root object in trailer", __name__)
  209. if self._validated_root is None:
  210. logger_warning('Searching object with "/Catalog" key', __name__)
  211. number_of_objects = cast(int, self.trailer.get("/Size", 0))
  212. for i in range(number_of_objects):
  213. if i >= self._root_object_recovery_limit:
  214. raise LimitReachedError("Maximum Root object recovery limit reached.")
  215. try:
  216. obj = self.get_object(i + 1)
  217. except Exception: # to be sure to capture all errors
  218. obj = None
  219. if isinstance(obj, DictionaryObject) and obj.get("/Type") == "/Catalog":
  220. self._validated_root = obj
  221. logger_warning(f"Root found at {obj.indirect_reference!r}", __name__)
  222. break
  223. if self._validated_root is None:
  224. if not is_null_or_none(root) and "/Pages" in cast(DictionaryObject, cast(PdfObject, root).get_object()):
  225. logger_warning(
  226. f"Possible root found at {cast(PdfObject, root).indirect_reference!r}, but missing /Catalog key",
  227. __name__
  228. )
  229. self._validated_root = cast(
  230. DictionaryObject, cast(PdfObject, root).get_object()
  231. )
  232. else:
  233. raise PdfReadError("Cannot find Root object in pdf")
  234. return self._validated_root
  235. @property
  236. def _info(self) -> Optional[DictionaryObject]:
  237. """
  238. Provide access to "/Info". Standardized with PdfWriter.
  239. Returns:
  240. /Info Dictionary; None if the entry does not exist
  241. """
  242. info = self.trailer.get(TK.INFO, None)
  243. if is_null_or_none(info):
  244. return None
  245. assert info is not None, "mypy"
  246. info = info.get_object()
  247. if not isinstance(info, DictionaryObject):
  248. raise PdfReadError(
  249. "Trailer not found or does not point to a document information dictionary"
  250. )
  251. return info
  252. @property
  253. def _ID(self) -> Optional[ArrayObject]:
  254. """
  255. Provide access to "/ID". Standardized with PdfWriter.
  256. Returns:
  257. /ID array; None if the entry does not exist
  258. """
  259. id = self.trailer.get(TK.ID, None)
  260. if is_null_or_none(id):
  261. return None
  262. assert id is not None, "mypy"
  263. return cast(ArrayObject, id.get_object())
  264. @property
  265. def pdf_header(self) -> str:
  266. """
  267. The first 8 bytes of the file.
  268. This is typically something like ``'%PDF-1.6'`` and can be used to
  269. detect if the file is actually a PDF file and which version it is.
  270. """
  271. # TODO: Make this return a bytes object for consistency
  272. # but that needs a deprecation
  273. loc = self.stream.tell()
  274. self.stream.seek(0, 0)
  275. pdf_file_version = self.stream.read(8).decode("utf-8", "backslashreplace")
  276. self.stream.seek(loc, 0) # return to where it was
  277. return pdf_file_version
  278. @property
  279. def xmp_metadata(self) -> Optional[XmpInformation]:
  280. """XMP (Extensible Metadata Platform) data."""
  281. try:
  282. self._override_encryption = True
  283. return cast(XmpInformation, self.root_object.xmp_metadata)
  284. finally:
  285. self._override_encryption = False
  286. def _get_page_number_by_indirect(
  287. self, indirect_reference: Union[None, int, NullObject, IndirectObject]
  288. ) -> Optional[int]:
  289. """
  290. Retrieve the page number from an indirect reference.
  291. Args:
  292. indirect_reference: The indirect reference to locate.
  293. Returns:
  294. Page number or None.
  295. """
  296. if self._page_id2num is None:
  297. self._page_id2num = {
  298. x.indirect_reference.idnum: i for i, x in enumerate(self.pages) # type: ignore
  299. }
  300. if is_null_or_none(indirect_reference):
  301. return None
  302. assert isinstance(indirect_reference, (int, IndirectObject)), "mypy"
  303. if isinstance(indirect_reference, int):
  304. idnum = indirect_reference
  305. else:
  306. idnum = indirect_reference.idnum
  307. assert self._page_id2num is not None, "hint for mypy"
  308. return self._page_id2num.get(idnum, None)
  309. def _get_object_from_stream(
  310. self, indirect_reference: IndirectObject
  311. ) -> Union[int, PdfObject, str]:
  312. # indirect reference to object in object stream
  313. # read the entire object stream into memory
  314. stmnum, _idx = self.xref_objStm[indirect_reference.idnum]
  315. obj_stm: EncodedStreamObject = IndirectObject(stmnum, 0, self).get_object() # type: ignore
  316. # This is an xref to a stream, so its type better be a stream
  317. assert cast(str, obj_stm["/Type"]) == "/ObjStm"
  318. # Parse ALL objects in this stream in one pass and cache them.
  319. # This avoids O(N²) behavior when many objects from the same stream
  320. # are resolved individually (each call would re-parse the header).
  321. stream_data = BytesIO(obj_stm.get_data())
  322. n = int(obj_stm["/N"]) # type: ignore[call-overload]
  323. first_offset = int(obj_stm["/First"]) # type: ignore[call-overload]
  324. # Phase 1: Read the index (objnum, offset) pairs from the header.
  325. obj_index: list[tuple[int, int]] = []
  326. for _i in range(n):
  327. read_non_whitespace(stream_data)
  328. stream_data.seek(-1, 1)
  329. objnum = NumberObject.read_from_stream(stream_data)
  330. read_non_whitespace(stream_data)
  331. stream_data.seek(-1, 1)
  332. offset = NumberObject.read_from_stream(stream_data)
  333. read_non_whitespace(stream_data)
  334. stream_data.seek(-1, 1)
  335. obj_index.append((int(objnum), int(offset)))
  336. # Phase 2: Parse each object and cache it.
  337. target_obj: Union[int, PdfObject, str] = NullObject()
  338. found = False
  339. for i, (obj_num, obj_offset) in enumerate(obj_index):
  340. # Skip objects already in the cache.
  341. cached = self.cache_get_indirect_object(0, obj_num)
  342. if cached is not None:
  343. if obj_num == indirect_reference.idnum:
  344. target_obj = cached
  345. found = True
  346. continue
  347. stream_data.seek(first_offset + obj_offset, 0)
  348. # To cope with case where the 'pointer' is on a white space
  349. read_non_whitespace(stream_data)
  350. stream_data.seek(-1, 1)
  351. try:
  352. obj = read_object(stream_data, self)
  353. except PdfStreamError as exc:
  354. # Stream object cannot be read. Normally, a critical error, but
  355. # Adobe Reader doesn't complain, so continue (in strict mode?)
  356. logger_warning(
  357. f"Invalid stream (index {i}) within object "
  358. f"{obj_num} 0: {exc}",
  359. __name__,
  360. )
  361. if self.strict: # pragma: no cover
  362. raise PdfReadError(
  363. f"Cannot read object stream: {exc}"
  364. ) # pragma: no cover
  365. obj = NullObject() # pragma: no cover
  366. # Only cache if this object is still registered in xref_objStm.
  367. # Incremental updates may override objects originally in the stream;
  368. # caching those stale versions would shadow the newer xref entry.
  369. if obj_num in self.xref_objStm:
  370. self.cache_indirect_object(0, obj_num, obj) # type: ignore[arg-type]
  371. if obj_num == indirect_reference.idnum:
  372. target_obj = obj
  373. found = True
  374. if not found and self.strict: # pragma: no cover
  375. raise PdfReadError(
  376. "This is a fatal error in strict mode."
  377. ) # pragma: no cover
  378. return target_obj
  379. def get_object(
  380. self, indirect_reference: Union[int, IndirectObject]
  381. ) -> Optional[PdfObject]:
  382. if isinstance(indirect_reference, int):
  383. indirect_reference = IndirectObject(indirect_reference, 0, self)
  384. retval = self.cache_get_indirect_object(
  385. indirect_reference.generation, indirect_reference.idnum
  386. )
  387. if retval is not None:
  388. return retval
  389. if (
  390. indirect_reference.generation == 0
  391. and indirect_reference.idnum in self.xref_objStm
  392. ):
  393. retval = self._get_object_from_stream(indirect_reference) # type: ignore
  394. elif (
  395. indirect_reference.generation in self.xref
  396. and indirect_reference.idnum in self.xref[indirect_reference.generation]
  397. ):
  398. if self.xref_free_entry.get(indirect_reference.generation, {}).get(
  399. indirect_reference.idnum, False
  400. ):
  401. return NullObject()
  402. start = self.xref[indirect_reference.generation][indirect_reference.idnum]
  403. self.stream.seek(start, 0)
  404. try:
  405. idnum, generation = self.read_object_header(self.stream)
  406. if (
  407. idnum != indirect_reference.idnum
  408. or generation != indirect_reference.generation
  409. ):
  410. raise PdfReadError("Not matching, we parse the file for it")
  411. except Exception:
  412. if hasattr(self.stream, "getbuffer"):
  413. buf = bytes(self.stream.getbuffer())
  414. else:
  415. p = self.stream.tell()
  416. self.stream.seek(0, 0)
  417. buf = self.stream.read(-1)
  418. self.stream.seek(p, 0)
  419. m = re.search(
  420. rf"\s{indirect_reference.idnum}\s+{indirect_reference.generation}\s+obj".encode(),
  421. buf,
  422. )
  423. if m is not None:
  424. logger_warning(
  425. f"Object ID {indirect_reference.idnum},{indirect_reference.generation} ref repaired",
  426. __name__,
  427. )
  428. self.xref[indirect_reference.generation][
  429. indirect_reference.idnum
  430. ] = (m.start(0) + 1)
  431. self.stream.seek(m.start(0) + 1)
  432. idnum, generation = self.read_object_header(self.stream)
  433. else:
  434. idnum = -1
  435. generation = -1 # exception will be raised below
  436. if idnum != indirect_reference.idnum and self.xref_index:
  437. # xref table probably had bad indexes due to not being zero-indexed
  438. if self.strict:
  439. raise PdfReadError(
  440. f"Expected object ID ({indirect_reference.idnum} {indirect_reference.generation}) "
  441. f"does not match actual ({idnum} {generation}); "
  442. "xref table not zero-indexed."
  443. )
  444. # xref table is corrected in non-strict mode
  445. elif idnum != indirect_reference.idnum and self.strict:
  446. # some other problem
  447. raise PdfReadError(
  448. f"Expected object ID ({indirect_reference.idnum} {indirect_reference.generation}) "
  449. f"does not match actual ({idnum} {generation})."
  450. )
  451. if self.strict:
  452. assert generation == indirect_reference.generation
  453. current_object = (indirect_reference.idnum, indirect_reference.generation)
  454. if current_object in self._known_objects:
  455. raise LimitReachedError(f"Detected loop with self reference for {indirect_reference!r}.")
  456. self._known_objects.add(current_object)
  457. retval = read_object(self.stream, self) # type: ignore
  458. self._known_objects.remove(current_object)
  459. # override encryption is used for the /Encrypt dictionary
  460. if not self._override_encryption and self._encryption is not None:
  461. # if we don't have the encryption key:
  462. if not self._encryption.is_decrypted():
  463. raise FileNotDecryptedError("File has not been decrypted")
  464. # otherwise, decrypt here...
  465. retval = cast(PdfObject, retval)
  466. retval = self._encryption.decrypt_object(
  467. retval, indirect_reference.idnum, indirect_reference.generation
  468. )
  469. else:
  470. if hasattr(self.stream, "getbuffer"):
  471. buf = bytes(self.stream.getbuffer())
  472. else:
  473. p = self.stream.tell()
  474. self.stream.seek(0, 0)
  475. buf = self.stream.read(-1)
  476. self.stream.seek(p, 0)
  477. m = re.search(
  478. rf"\s{indirect_reference.idnum}\s+{indirect_reference.generation}\s+obj".encode(),
  479. buf,
  480. )
  481. if m is not None:
  482. logger_warning(
  483. f"Object {indirect_reference.idnum} {indirect_reference.generation} found",
  484. __name__,
  485. )
  486. if indirect_reference.generation not in self.xref:
  487. self.xref[indirect_reference.generation] = {}
  488. self.xref[indirect_reference.generation][indirect_reference.idnum] = (
  489. m.start(0) + 1
  490. )
  491. self.stream.seek(m.end(0) + 1)
  492. skip_over_whitespace(self.stream)
  493. self.stream.seek(-1, 1)
  494. retval = read_object(self.stream, self) # type: ignore
  495. # override encryption is used for the /Encrypt dictionary
  496. if not self._override_encryption and self._encryption is not None:
  497. # if we don't have the encryption key:
  498. if not self._encryption.is_decrypted():
  499. raise FileNotDecryptedError("File has not been decrypted")
  500. # otherwise, decrypt here...
  501. retval = cast(PdfObject, retval)
  502. retval = self._encryption.decrypt_object(
  503. retval, indirect_reference.idnum, indirect_reference.generation
  504. )
  505. else:
  506. logger_warning(
  507. f"Object {indirect_reference.idnum} {indirect_reference.generation} not defined.",
  508. __name__,
  509. )
  510. if self.strict:
  511. raise PdfReadError("Could not find object.")
  512. # For ObjStm objects, _get_object_from_stream already cached
  513. # the result during batch parsing; skip the redundant cache write
  514. # to avoid "Overwriting cache" warnings. For non-ObjStm objects
  515. # (including encrypted ones that need decrypted values cached),
  516. # always write.
  517. if not (
  518. indirect_reference.generation == 0
  519. and indirect_reference.idnum in self.xref_objStm
  520. ):
  521. self.cache_indirect_object(
  522. indirect_reference.generation, indirect_reference.idnum, retval
  523. )
  524. return retval
  525. def read_object_header(self, stream: StreamType) -> tuple[int, int]:
  526. # Should never be necessary to read out whitespace, since the
  527. # cross-reference table should put us in the right spot to read the
  528. # object header. In reality some files have stupid cross-reference
  529. # tables that are off by whitespace bytes.
  530. skip_over_comment(stream)
  531. extra = skip_over_whitespace(stream)
  532. stream.seek(-1, 1)
  533. idnum = read_until_whitespace(stream)
  534. extra |= skip_over_whitespace(stream)
  535. stream.seek(-1, 1)
  536. generation = read_until_whitespace(stream)
  537. extra |= skip_over_whitespace(stream)
  538. stream.seek(-1, 1)
  539. # although it's not used, it might still be necessary to read
  540. _obj = stream.read(3)
  541. read_non_whitespace(stream)
  542. stream.seek(-1, 1)
  543. if extra and self.strict:
  544. logger_warning(
  545. f"Superfluous whitespace found in object header {idnum} {generation}", # type: ignore
  546. __name__,
  547. )
  548. return int(idnum), int(generation)
  549. def cache_get_indirect_object(
  550. self, generation: int, idnum: int
  551. ) -> Optional[PdfObject]:
  552. try:
  553. return self.resolved_objects.get((generation, idnum))
  554. except RecursionError:
  555. raise PdfReadError("Maximum recursion depth reached.")
  556. def cache_indirect_object(
  557. self, generation: int, idnum: int, obj: Optional[PdfObject]
  558. ) -> Optional[PdfObject]:
  559. if (generation, idnum) in self.resolved_objects:
  560. msg = f"Overwriting cache for {generation} {idnum}"
  561. if self.strict:
  562. raise PdfReadError(msg)
  563. logger_warning(msg, __name__)
  564. self.resolved_objects[(generation, idnum)] = obj
  565. if obj is not None:
  566. obj.indirect_reference = IndirectObject(idnum, generation, self)
  567. return obj
  568. def _replace_object(self, indirect_reference: IndirectObject, obj: PdfObject) -> PdfObject:
  569. # function reserved for future development
  570. if indirect_reference.pdf != self:
  571. raise ValueError("Cannot update PdfReader with external object")
  572. if (indirect_reference.generation, indirect_reference.idnum) not in self.resolved_objects:
  573. raise ValueError("Cannot find referenced object")
  574. self.resolved_objects[(indirect_reference.generation, indirect_reference.idnum)] = obj
  575. obj.indirect_reference = indirect_reference
  576. return obj
  577. def read(self, stream: StreamType) -> None:
  578. """
  579. Read and process the PDF stream, extracting necessary data.
  580. Args:
  581. stream: The PDF file stream.
  582. """
  583. self._basic_validation(stream)
  584. self._find_eof_marker(stream)
  585. startxref = self._find_startxref_pos(stream)
  586. self._startxref = startxref
  587. # check and eventually correct the startxref only if not strict
  588. xref_issue_nr = self._get_xref_issues(stream, startxref)
  589. if xref_issue_nr != 0:
  590. if self.strict and xref_issue_nr:
  591. raise PdfReadError("Broken xref table")
  592. logger_warning(f"incorrect startxref pointer({xref_issue_nr})", __name__)
  593. # read all cross-reference tables and their trailers
  594. self._read_xref_tables_and_trailers(stream, startxref, xref_issue_nr)
  595. # if not zero-indexed, verify that the table is correct; change it if necessary
  596. if self.xref_index and not self.strict:
  597. loc = stream.tell()
  598. for gen, xref_entry in self.xref.items():
  599. if gen == 65535:
  600. continue
  601. xref_k = sorted(
  602. xref_entry.keys()
  603. ) # ensure ascending to prevent damage
  604. for id in xref_k:
  605. stream.seek(xref_entry[id], 0)
  606. try:
  607. pid, _pgen = self.read_object_header(stream)
  608. except ValueError:
  609. self._rebuild_xref_table(stream)
  610. break
  611. if pid == id - self.xref_index:
  612. # fixing index item per item is required for revised PDF.
  613. self.xref[gen][pid] = self.xref[gen][id]
  614. del self.xref[gen][id]
  615. # if not, then either it's just plain wrong, or the
  616. # non-zero-index is actually correct
  617. stream.seek(loc, 0) # return to where it was
  618. # remove wrong objects (not pointing to correct structures) - cf #2326
  619. if not self.strict:
  620. loc = stream.tell()
  621. for gen, xref_entry in self.xref.items():
  622. if gen == 65535:
  623. continue
  624. ids = list(xref_entry.keys())
  625. for id in ids:
  626. stream.seek(xref_entry[id], 0)
  627. try:
  628. self.read_object_header(stream)
  629. except ValueError:
  630. logger_warning(
  631. f"Ignoring wrong pointing object {id} {gen} (offset {xref_entry[id]})",
  632. __name__,
  633. )
  634. del xref_entry[id] # we can delete the id, we are parsing ids
  635. stream.seek(loc, 0) # return to where it was
  636. def _basic_validation(self, stream: StreamType) -> None:
  637. """Ensure the stream is valid and not empty."""
  638. stream.seek(0, os.SEEK_SET)
  639. try:
  640. header_byte = stream.read(5)
  641. except UnicodeDecodeError:
  642. raise UnsupportedOperation("cannot read header")
  643. if header_byte == b"":
  644. raise EmptyFileError("Cannot read an empty file")
  645. if header_byte != b"%PDF-":
  646. if self.strict:
  647. raise PdfReadError(
  648. f"PDF starts with '{header_byte.decode('utf8')}', "
  649. "but '%PDF-' expected"
  650. )
  651. logger_warning(f"invalid pdf header: {header_byte}", __name__)
  652. stream.seek(0, os.SEEK_END)
  653. def _find_eof_marker(self, stream: StreamType) -> None:
  654. """
  655. Jump to the %%EOF marker.
  656. According to the specs, the %%EOF marker should be at the very end of
  657. the file. Hence for standard-compliant PDF documents this function will
  658. read only the last part (DEFAULT_BUFFER_SIZE).
  659. """
  660. HEADER_SIZE = 8 # to parse whole file, Header is e.g. '%PDF-1.6'
  661. line = b""
  662. first = True
  663. while not line.startswith(b"%%EOF"):
  664. if line != b"" and first:
  665. if any(
  666. line.strip().endswith(tr) for tr in (b"%%EO", b"%%E", b"%%", b"%")
  667. ):
  668. # Consider the file as truncated while
  669. # having enough confidence to carry on.
  670. logger_warning("EOF marker seems truncated", __name__)
  671. break
  672. first = False
  673. if b"startxref" in line:
  674. logger_warning(
  675. "CAUTION: startxref found while searching for %%EOF. "
  676. "The file might be truncated and some data might not be read.",
  677. __name__,
  678. )
  679. if stream.tell() < HEADER_SIZE:
  680. if self.strict:
  681. raise PdfReadError("EOF marker not found")
  682. logger_warning("EOF marker not found", __name__)
  683. line = read_previous_line(stream)
  684. def _find_startxref_pos(self, stream: StreamType) -> int:
  685. """
  686. Find startxref entry - the location of the xref table.
  687. Args:
  688. stream:
  689. Returns:
  690. The bytes offset
  691. """
  692. line = read_previous_line(stream)
  693. try:
  694. startxref = int(line)
  695. except ValueError:
  696. # 'startxref' may be on the same line as the location
  697. if not line.startswith(b"startxref"):
  698. raise PdfReadError("startxref not found")
  699. startxref = int(line[9:].strip())
  700. logger_warning("startxref on same line as offset", __name__)
  701. else:
  702. line = read_previous_line(stream)
  703. if not line.startswith(b"startxref"):
  704. raise PdfReadError("startxref not found")
  705. return startxref
  706. def _read_standard_xref_table(self, stream: StreamType) -> None:
  707. # standard cross-reference table
  708. ref = stream.read(3)
  709. if ref != b"ref":
  710. raise PdfReadError("xref table read error")
  711. read_non_whitespace(stream)
  712. stream.seek(-1, 1)
  713. first_time = True # check if the first time looking at the xref table
  714. while True:
  715. num = cast(int, read_object(stream, self))
  716. if first_time and num != 0:
  717. self.xref_index = num
  718. if self.strict:
  719. logger_warning(
  720. "Xref table not zero-indexed. ID numbers for objects will be corrected.",
  721. __name__,
  722. )
  723. # if table not zero indexed, could be due to error from when PDF was created
  724. # which will lead to mismatched indices later on, only warned and corrected if self.strict==True
  725. first_time = False
  726. read_non_whitespace(stream)
  727. stream.seek(-1, 1)
  728. size = cast(int, read_object(stream, self))
  729. if not isinstance(size, int):
  730. logger_warning(
  731. "Invalid/Truncated xref table. Rebuilding it.",
  732. __name__,
  733. )
  734. self._rebuild_xref_table(stream)
  735. stream.read()
  736. return
  737. read_non_whitespace(stream)
  738. stream.seek(-1, 1)
  739. cnt = 0
  740. while cnt < size:
  741. line = stream.read(20)
  742. if not line:
  743. raise PdfReadError("Unexpected empty line in Xref table.")
  744. # It's very clear in section 3.4.3 of the PDF spec
  745. # that all cross-reference table lines are a fixed
  746. # 20 bytes (as of PDF 1.7). However, some files have
  747. # 21-byte entries (or more) due to the use of \r\n
  748. # (CRLF) EOL's. Detect that case, and adjust the line
  749. # until it does not begin with a \r (CR) or \n (LF).
  750. while line[0] in b"\x0D\x0A":
  751. stream.seek(-20 + 1, 1)
  752. line = stream.read(20)
  753. # On the other hand, some malformed PDF files
  754. # use a single character EOL without a preceding
  755. # space. Detect that case, and seek the stream
  756. # back one character (0-9 means we've bled into
  757. # the next xref entry, t means we've bled into the
  758. # text "trailer"):
  759. if line[-1] in b"0123456789t":
  760. stream.seek(-1, 1)
  761. try:
  762. offset_b, generation_b = line[:16].split(b" ")
  763. entry_type_b = line[17:18]
  764. offset, generation = int(offset_b), int(generation_b)
  765. except Exception:
  766. if hasattr(stream, "getbuffer"):
  767. buf = bytes(stream.getbuffer())
  768. else:
  769. p = stream.tell()
  770. stream.seek(0, 0)
  771. buf = stream.read(-1)
  772. stream.seek(p)
  773. f = re.search(rf"{num}\s+(\d+)\s+obj".encode(), buf)
  774. if f is None:
  775. logger_warning(
  776. f"entry {num} in Xref table invalid; object not found",
  777. __name__,
  778. )
  779. generation = 65535
  780. offset = -1
  781. entry_type_b = b"f"
  782. else:
  783. logger_warning(
  784. f"entry {num} in Xref table invalid but object found",
  785. __name__,
  786. )
  787. generation = int(f.group(1))
  788. offset = f.start()
  789. if generation not in self.xref:
  790. self.xref[generation] = {}
  791. self.xref_free_entry[generation] = {}
  792. if num in self.xref[generation]:
  793. # It really seems like we should allow the last
  794. # xref table in the file to override previous
  795. # ones. Since we read the file backwards, assume
  796. # any existing key is already set correctly.
  797. pass
  798. else:
  799. if entry_type_b == b"n":
  800. self.xref[generation][num] = offset
  801. try:
  802. self.xref_free_entry[generation][num] = entry_type_b == b"f"
  803. except Exception:
  804. pass
  805. try:
  806. self.xref_free_entry[65535][num] = entry_type_b == b"f"
  807. except Exception:
  808. pass
  809. cnt += 1
  810. num += 1
  811. read_non_whitespace(stream)
  812. stream.seek(-1, 1)
  813. trailer_tag = stream.read(7)
  814. if trailer_tag != b"trailer":
  815. # more xrefs!
  816. stream.seek(-7, 1)
  817. else:
  818. break
  819. def _read_xref_tables_and_trailers(
  820. self, stream: StreamType, startxref: Optional[int], xref_issue_nr: int
  821. ) -> None:
  822. """Read the cross-reference tables and trailers in the PDF stream."""
  823. self.xref = {}
  824. self.xref_free_entry = {}
  825. self.xref_objStm = {}
  826. self.trailer = DictionaryObject()
  827. visited_xref_offsets: set[int] = set()
  828. while startxref is not None:
  829. # Detect circular /Prev references in the xref chain
  830. if startxref in visited_xref_offsets:
  831. logger_warning(
  832. f"Circular xref chain detected at offset {startxref}, stopping",
  833. __name__,
  834. )
  835. break
  836. visited_xref_offsets.add(startxref)
  837. # load the xref table
  838. stream.seek(startxref, 0)
  839. x = stream.read(1)
  840. if x in b"\r\n":
  841. x = stream.read(1)
  842. if x == b"x":
  843. startxref = self._read_xref(stream)
  844. elif xref_issue_nr:
  845. try:
  846. self._rebuild_xref_table(stream)
  847. break
  848. except Exception:
  849. xref_issue_nr = 0
  850. elif x.isdigit():
  851. try:
  852. xrefstream = self._read_pdf15_xref_stream(stream)
  853. except Exception as e:
  854. if TK.ROOT in self.trailer:
  855. logger_warning(
  856. f"Previous trailer cannot be read: {e.args}", __name__
  857. )
  858. break
  859. raise PdfReadError(f"Trailer cannot be read: {e!s}")
  860. self._process_xref_stream(xrefstream)
  861. if "/Prev" in xrefstream:
  862. startxref = cast(int, xrefstream["/Prev"])
  863. else:
  864. break
  865. else:
  866. startxref = self._read_xref_other_error(stream, startxref)
  867. def _process_xref_stream(self, xrefstream: DictionaryObject) -> None:
  868. """Process and handle the xref stream."""
  869. trailer_keys = TK.ROOT, TK.ENCRYPT, TK.INFO, TK.ID, TK.SIZE
  870. for key in trailer_keys:
  871. if key in xrefstream and key not in self.trailer:
  872. self.trailer[NameObject(key)] = xrefstream.raw_get(key)
  873. if "/XRefStm" in xrefstream:
  874. p = self.stream.tell()
  875. self.stream.seek(cast(int, xrefstream["/XRefStm"]) + 1, 0)
  876. self._read_pdf15_xref_stream(self.stream)
  877. self.stream.seek(p, 0)
  878. def _read_xref(self, stream: StreamType) -> Optional[int]:
  879. self._read_standard_xref_table(stream)
  880. if stream.read(1) == b"":
  881. return None
  882. stream.seek(-1, 1)
  883. read_non_whitespace(stream)
  884. stream.seek(-1, 1)
  885. new_trailer = cast(dict[str, Any], read_object(stream, self))
  886. for key, value in new_trailer.items():
  887. if key not in self.trailer:
  888. self.trailer[key] = value
  889. if "/XRefStm" in new_trailer:
  890. p = stream.tell()
  891. stream.seek(cast(int, new_trailer["/XRefStm"]) + 1, 0)
  892. try:
  893. self._read_pdf15_xref_stream(stream)
  894. except Exception:
  895. logger_warning(
  896. f"XRef object at {new_trailer['/XRefStm']} can not be read, some object may be missing",
  897. __name__,
  898. )
  899. stream.seek(p, 0)
  900. if "/Prev" in new_trailer:
  901. return new_trailer["/Prev"]
  902. return None
  903. def _read_xref_other_error(
  904. self, stream: StreamType, startxref: int
  905. ) -> Optional[int]:
  906. # some PDFs have /Prev=0 in the trailer, instead of no /Prev
  907. if startxref == 0:
  908. if self.strict:
  909. raise PdfReadError(
  910. "/Prev=0 in the trailer (try opening with strict=False)"
  911. )
  912. logger_warning(
  913. "/Prev=0 in the trailer - assuming there is no previous xref table",
  914. __name__,
  915. )
  916. return None
  917. # bad xref character at startxref. Let's see if we can find
  918. # the xref table nearby, as we've observed this error with an
  919. # off-by-one before.
  920. stream.seek(-11, 1)
  921. tmp = stream.read(20)
  922. xref_loc = tmp.find(b"xref")
  923. if xref_loc != -1:
  924. startxref -= 10 - xref_loc
  925. return startxref
  926. # No explicit xref table, try finding a cross-reference stream.
  927. stream.seek(startxref, 0)
  928. for look in range(25): # value extended to cope with more linearized files
  929. if stream.read(1).isdigit():
  930. # This is not a standard PDF, consider adding a warning
  931. startxref += look
  932. return startxref
  933. # no xref table found at specified location
  934. if "/Root" in self.trailer and not self.strict:
  935. # if Root has been already found, just raise warning
  936. logger_warning("Invalid parent xref., rebuild xref", __name__)
  937. try:
  938. self._rebuild_xref_table(stream)
  939. return None
  940. except Exception:
  941. raise PdfReadError("Cannot rebuild xref")
  942. raise PdfReadError("Could not find xref table at specified location")
  943. def _read_pdf15_xref_stream(
  944. self, stream: StreamType
  945. ) -> Union[ContentStream, EncodedStreamObject, DecodedStreamObject]:
  946. """Read the cross-reference stream for PDF 1.5+."""
  947. stream.seek(-1, 1)
  948. idnum, generation = self.read_object_header(stream)
  949. xrefstream = cast(ContentStream, read_object(stream, self))
  950. if cast(str, xrefstream["/Type"]) != "/XRef":
  951. raise PdfReadError(f"Unexpected type {xrefstream['/Type']!r}")
  952. self.cache_indirect_object(generation, idnum, xrefstream)
  953. # Index pairs specify the subsections in the dictionary.
  954. # If none, create one subsection that spans everything.
  955. if "/Size" not in xrefstream:
  956. # According to table 17 of the PDF 2.0 specification, this key is required.
  957. raise PdfReadError(f"Size missing from XRef stream {xrefstream!r}!")
  958. idx_pairs = xrefstream.get("/Index", [0, xrefstream["/Size"]])
  959. entry_sizes = cast(dict[Any, Any], xrefstream.get("/W"))
  960. assert len(entry_sizes) >= 3
  961. if self.strict and len(entry_sizes) > 3:
  962. raise PdfReadError(f"Too many entry sizes: {entry_sizes}")
  963. stream_data = BytesIO(xrefstream.get_data())
  964. def get_entry(i: int) -> Union[int, tuple[int, ...]]:
  965. # Reads the correct number of bytes for each entry. See the
  966. # discussion of the W parameter in PDF spec table 17.
  967. if entry_sizes[i] > 0:
  968. d = stream_data.read(entry_sizes[i])
  969. return convert_to_int(d, entry_sizes[i])
  970. # PDF Spec Table 17: A value of zero for an element in the
  971. # W array indicates...the default value shall be used
  972. if i == 0:
  973. return 1 # First value defaults to 1
  974. return 0
  975. def used_before(num: int, generation: Union[int, tuple[int, ...]]) -> bool:
  976. # We move backwards through the xrefs, don't replace any.
  977. return num in self.xref.get(generation, []) or num in self.xref_objStm # type: ignore
  978. # Iterate through each subsection
  979. self._read_xref_subsections(idx_pairs, get_entry, used_before)
  980. return xrefstream
  981. @staticmethod
  982. def _get_xref_issues(stream: StreamType, startxref: int) -> int:
  983. """
  984. Return an int which indicates an issue. 0 means there is no issue.
  985. Args:
  986. stream:
  987. startxref:
  988. Returns:
  989. 0 means no issue, other values represent specific issues.
  990. """
  991. if startxref == 0:
  992. return 4
  993. stream.seek(startxref - 1, 0) # -1 to check character before
  994. line = stream.read(1)
  995. if line == b"j":
  996. line = stream.read(1)
  997. if line not in b"\r\n \t":
  998. return 1
  999. line = stream.read(4)
  1000. if line != b"xref":
  1001. # not a xref so check if it is an XREF object
  1002. line = b""
  1003. while line in b"0123456789 \t":
  1004. line = stream.read(1)
  1005. if line == b"":
  1006. return 2
  1007. line += stream.read(2) # 1 char already read, +2 to check "obj"
  1008. if line.lower() != b"obj":
  1009. return 3
  1010. return 0
  1011. @classmethod
  1012. def _find_pdf_objects(cls, data: bytes) -> Iterable[tuple[int, int, int]]:
  1013. index = 0
  1014. ord_0 = ord("0")
  1015. ord_9 = ord("9")
  1016. while True:
  1017. index = data.find(b" obj", index)
  1018. if index == -1:
  1019. return
  1020. index_before_space = index - 1
  1021. # Skip whitespace backwards
  1022. while index_before_space >= 0 and data[index_before_space] in WHITESPACES_AS_BYTES:
  1023. index_before_space -= 1
  1024. # Read generation number
  1025. generation_end = index_before_space + 1
  1026. while index_before_space >= 0 and ord_0 <= data[index_before_space] <= ord_9:
  1027. index_before_space -= 1
  1028. generation_start = index_before_space + 1
  1029. # Skip whitespace
  1030. while index_before_space >= 0 and data[index_before_space] in WHITESPACES_AS_BYTES:
  1031. index_before_space -= 1
  1032. # Read object number
  1033. object_end = index_before_space + 1
  1034. while index_before_space >= 0 and ord_0 <= data[index_before_space] <= ord_9:
  1035. index_before_space -= 1
  1036. object_start = index_before_space + 1
  1037. # Validate
  1038. if object_start < object_end and generation_start < generation_end:
  1039. object_number = int(data[object_start:object_end])
  1040. generation_number = int(data[generation_start:generation_end])
  1041. yield object_number, generation_number, object_start
  1042. index += 4 # len(b" obj")
  1043. @classmethod
  1044. def _find_pdf_trailers(cls, data: bytes) -> Iterable[int]:
  1045. index = 0
  1046. data_length = len(data)
  1047. while True:
  1048. index = data.find(b"trailer", index)
  1049. if index == -1:
  1050. return
  1051. index_after_trailer = index + 7 # len(b"trailer")
  1052. # Skip whitespace
  1053. while index_after_trailer < data_length and data[index_after_trailer] in WHITESPACES_AS_BYTES:
  1054. index_after_trailer += 1
  1055. # Must be dictionary start
  1056. if index_after_trailer + 1 < data_length and data[index_after_trailer:index_after_trailer+2] == b"<<":
  1057. yield index_after_trailer # offset of '<<'
  1058. index += 7 # len(b"trailer")
  1059. def _rebuild_xref_table(self, stream: StreamType) -> None:
  1060. self.xref = {}
  1061. stream.seek(0, 0)
  1062. stream_data = stream.read(-1)
  1063. for object_number, generation_number, object_start in self._find_pdf_objects(stream_data):
  1064. if generation_number not in self.xref:
  1065. self.xref[generation_number] = {}
  1066. self.xref[generation_number][object_number] = object_start
  1067. logger_warning("parsing for Object Streams", __name__)
  1068. for generation_number in self.xref:
  1069. for object_number in self.xref[generation_number]:
  1070. # get_object in manual
  1071. stream.seek(self.xref[generation_number][object_number], 0)
  1072. try:
  1073. _ = self.read_object_header(stream)
  1074. obj = cast(StreamObject, read_object(stream, self))
  1075. if obj.get("/Type", "") != "/ObjStm":
  1076. continue
  1077. object_stream = BytesIO(obj.get_data())
  1078. actual_count = 0
  1079. while True:
  1080. current = read_until_whitespace(object_stream)
  1081. if not current.isdigit():
  1082. break
  1083. inner_object_number = int(current)
  1084. skip_over_whitespace(object_stream)
  1085. object_stream.seek(-1, 1)
  1086. current = read_until_whitespace(object_stream)
  1087. if not current.isdigit(): # pragma: no cover
  1088. break # pragma: no cover
  1089. inner_generation_number = int(current)
  1090. self.xref_objStm[inner_object_number] = (object_number, inner_generation_number)
  1091. actual_count += 1
  1092. if actual_count != obj.get("/N"): # pragma: no cover
  1093. logger_warning( # pragma: no cover
  1094. f"found {actual_count} objects within Object({object_number},{generation_number})"
  1095. f" whereas {obj.get('/N')} expected",
  1096. __name__,
  1097. )
  1098. except Exception: # could be multiple causes
  1099. pass
  1100. stream.seek(0, 0)
  1101. for position in self._find_pdf_trailers(stream_data):
  1102. stream.seek(position, 0)
  1103. new_trailer = cast(dict[Any, Any], read_object(stream, self))
  1104. # Here, we are parsing the file from start to end, the new data have to erase the existing.
  1105. for key, value in new_trailer.items():
  1106. self.trailer[key] = value
  1107. def _read_xref_subsections(
  1108. self,
  1109. idx_pairs: list[int],
  1110. get_entry: Callable[[int], Union[int, tuple[int, ...]]],
  1111. used_before: Callable[[int, Union[int, tuple[int, ...]]], bool],
  1112. ) -> None:
  1113. """Read and process the subsections of the xref."""
  1114. for start, size in self._pairs(idx_pairs):
  1115. # The subsections must increase
  1116. for num in range(start, start + size):
  1117. # The first entry is the type
  1118. xref_type = get_entry(0)
  1119. # The rest of the elements depend on the xref_type
  1120. if xref_type == 0:
  1121. # linked list of free objects
  1122. next_free_object = get_entry(1) # noqa: F841
  1123. next_generation = get_entry(2) # noqa: F841
  1124. elif xref_type == 1:
  1125. # objects that are in use but are not compressed
  1126. byte_offset = get_entry(1)
  1127. generation = get_entry(2)
  1128. if generation not in self.xref:
  1129. self.xref[generation] = {} # type: ignore
  1130. if not used_before(num, generation):
  1131. self.xref[generation][num] = byte_offset # type: ignore
  1132. elif xref_type == 2:
  1133. # compressed objects
  1134. objstr_num = get_entry(1)
  1135. obstr_idx = get_entry(2)
  1136. generation = 0 # PDF spec table 18, generation is 0
  1137. if not used_before(num, generation):
  1138. self.xref_objStm[num] = (objstr_num, obstr_idx)
  1139. elif self.strict:
  1140. raise PdfReadError(f"Unknown xref type: {xref_type}")
  1141. def _pairs(self, array: list[int]) -> Iterable[tuple[int, int]]:
  1142. """Iterate over pairs in the array."""
  1143. i = 0
  1144. while i + 1 < len(array):
  1145. yield array[i], array[i + 1]
  1146. i += 2
  1147. def decrypt(self, password: Union[str, bytes]) -> PasswordType:
  1148. """
  1149. When using an encrypted / secured PDF file with the PDF Standard
  1150. encryption handler, this function will allow the file to be decrypted.
  1151. It checks the given password against the document's user password and
  1152. owner password, and then stores the resulting decryption key if either
  1153. password is correct.
  1154. It does not matter which password was matched. Both passwords provide
  1155. the correct decryption key that will allow the document to be used with
  1156. this library.
  1157. Args:
  1158. password: The password to match.
  1159. Returns:
  1160. An indicator if the document was decrypted and whether it was the
  1161. owner password or the user password.
  1162. """
  1163. if not self._encryption:
  1164. raise PdfReadError("Not encrypted file")
  1165. # TODO: raise Exception for wrong password
  1166. return self._encryption.verify(password)
  1167. @property
  1168. def is_encrypted(self) -> bool:
  1169. """
  1170. Read-only boolean property showing whether this PDF file is encrypted.
  1171. Note that this property, if true, will remain true even after the
  1172. :meth:`decrypt()<pypdf.PdfReader.decrypt>` method is called.
  1173. """
  1174. return TK.ENCRYPT in self.trailer
  1175. def add_form_topname(self, name: str) -> Optional[DictionaryObject]:
  1176. """
  1177. Add a top level form that groups all form fields below it.
  1178. Args:
  1179. name: text string of the "/T" Attribute of the created object
  1180. Returns:
  1181. The created object. ``None`` means no object was created.
  1182. """
  1183. catalog = self.root_object
  1184. if "/AcroForm" not in catalog or not isinstance(
  1185. catalog["/AcroForm"], DictionaryObject
  1186. ):
  1187. return None
  1188. acroform = cast(DictionaryObject, catalog[NameObject("/AcroForm")])
  1189. if "/Fields" not in acroform:
  1190. # TODO: No error but this may be extended for XFA Forms
  1191. return None
  1192. interim = DictionaryObject()
  1193. interim[NameObject("/T")] = TextStringObject(name)
  1194. interim[NameObject("/Kids")] = acroform[NameObject("/Fields")]
  1195. self.cache_indirect_object(
  1196. 0,
  1197. max(i for (g, i) in self.resolved_objects if g == 0) + 1,
  1198. interim,
  1199. )
  1200. arr = ArrayObject()
  1201. arr.append(interim.indirect_reference)
  1202. acroform[NameObject("/Fields")] = arr
  1203. for o in cast(ArrayObject, interim["/Kids"]):
  1204. obj = o.get_object()
  1205. if "/Parent" in obj:
  1206. logger_warning(
  1207. f"Top Level Form Field {obj.indirect_reference} have a non-expected parent",
  1208. __name__,
  1209. )
  1210. obj[NameObject("/Parent")] = interim.indirect_reference
  1211. return interim
  1212. def rename_form_topname(self, name: str) -> Optional[DictionaryObject]:
  1213. """
  1214. Rename top level form field that all form fields below it.
  1215. Args:
  1216. name: text string of the "/T" field of the created object
  1217. Returns:
  1218. The modified object. ``None`` means no object was modified.
  1219. """
  1220. catalog = self.root_object
  1221. if "/AcroForm" not in catalog or not isinstance(
  1222. catalog["/AcroForm"], DictionaryObject
  1223. ):
  1224. return None
  1225. acroform = cast(DictionaryObject, catalog[NameObject("/AcroForm")])
  1226. if "/Fields" not in acroform:
  1227. return None
  1228. interim = cast(
  1229. DictionaryObject,
  1230. cast(ArrayObject, acroform[NameObject("/Fields")])[0].get_object(),
  1231. )
  1232. interim[NameObject("/T")] = TextStringObject(name)
  1233. return interim
  1234. def _repr_mimebundle_(
  1235. self,
  1236. include: Union[None, Iterable[str]] = None,
  1237. exclude: Union[None, Iterable[str]] = None,
  1238. ) -> dict[str, Any]:
  1239. """
  1240. Integration into Jupyter Notebooks.
  1241. This method returns a dictionary that maps a mime-type to its
  1242. representation.
  1243. .. seealso::
  1244. https://ipython.readthedocs.io/en/stable/config/integrating.html
  1245. """
  1246. self.stream.seek(0)
  1247. pdf_data = self.stream.read()
  1248. data = {
  1249. "application/pdf": pdf_data,
  1250. }
  1251. if include is not None:
  1252. # Filter representations based on include list
  1253. data = {k: v for k, v in data.items() if k in include}
  1254. if exclude is not None:
  1255. # Remove representations based on exclude list
  1256. data = {k: v for k, v in data.items() if k not in exclude}
  1257. return data