From 748ff31dd9499223aff5da5d4418ef45a8bd0a72 Mon Sep 17 00:00:00 2001 From: Jeremy Singer-Vine Date: Thu, 27 Mar 2025 21:58:17 -0400 Subject: [PATCH] More broadly handle RecursionError, via oss-fuzz --- pdfplumber/page.py | 5 +---- pdfplumber/utils/pdfinternals.py | 7 ++++++- .../pdfs/from-oss-fuzz/load/4646567755972608.pdf | Bin 0 -> 2746 bytes 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 tests/pdfs/from-oss-fuzz/load/4646567755972608.pdf diff --git a/pdfplumber/page.py b/pdfplumber/page.py index 600a2b1..71330d8 100644 --- a/pdfplumber/page.py +++ b/pdfplumber/page.py @@ -348,10 +348,7 @@ class Page(Container): parsed["data"] = annot return parsed - try: - raw = resolve_all(self.page_obj.annots) or [] - except RecursionError: - raise MalformedPDFException("Annotations are infinitely recursive.") + raw = resolve_all(self.page_obj.annots) or [] parsed = list(map(parse, raw)) if isinstance(self, CroppedPage): return self._crop_fn(parsed) diff --git a/pdfplumber/utils/pdfinternals.py b/pdfplumber/utils/pdfinternals.py index 2ba5064..7b278ce 100644 --- a/pdfplumber/utils/pdfinternals.py +++ b/pdfplumber/utils/pdfinternals.py @@ -4,6 +4,8 @@ from pdfminer.pdftypes import PDFObjRef from pdfminer.psparser import PSLiteral from pdfminer.utils import PDFDocEncoding +from .exceptions import MalformedPDFException + def decode_text(s: Union[bytes, str]) -> str: """ @@ -72,7 +74,10 @@ def resolve_all(x: Any) -> Any: if get_dict_type(resolved) == "Page": return x - return resolve_all(resolved) + try: + return resolve_all(resolved) + except RecursionError as e: + raise MalformedPDFException(e) elif isinstance(x, (list, tuple)): return type(x)(resolve_all(v) for v in x) elif isinstance(x, dict): diff --git a/tests/pdfs/from-oss-fuzz/load/4646567755972608.pdf b/tests/pdfs/from-oss-fuzz/load/4646567755972608.pdf new file mode 100644 index 0000000000000000000000000000000000000000..140b61d80eac109d93c1a3a0c9210a95deba92cb GIT binary patch literal 2746 zcmcIm&2HO95YDmPb01)GaMB*E{o$Wj7y@kCYK=sQAPeY02vGg36(y{gQY*mDp-A!s z1$qn+q{p862EFvwQ{TWqUZH5O?#z-)Qnru^MZ17R?hkinzWu(L-RmEB_hrjx5(H$E zrwriW0Qm6oxdxt$%{a~GIEIZsTAOsS)GJW9bWjfV$8#L<8Y5>WUr+DcxxO~TT@_|h6F2>{#!E+fdbSuJWfw3KxH6OqqF6mH zCm$?kD;W9Y21tr@;AWC+y)>Y3U2Th^E!%*GUB^A`P`#W5Lba6_hOrW{*Y-n?u-hr9 zJwjgvLCa&liv2i@R2-Ip9vrap7${7}`{P`rt6nVqz=pv1K(DfOKE(sT#pn>PLAb_d zQD}=ln{7E=HM`AsI%%{*=UMFE=`^ReyeoK2X3H}rc{#~pt*cKxpUfOxdS2epNZ zT%TXl@h*W0+hi!v*{Z7oAwUHojhKAANOf-AIa|EY5FlX0MoR<6PnYKzQh8rVKSDaA zm6aLihHNm!_V!L5bce_*UWAS+Z^7T{pAxI_#&|wgGLF2odAAPH#%X-P_ zF{n-lj*b(giMK%x)NZC7IKA^wIl+x_(PewxBW!(@DK`V3Z2vfQMXcRzz6 ziTs4NIO!#Hc$@l7vZXVR+UeY?+7ujIfv^@}gMh&Mc{V-M21XodjX6YLm{Mj= zImMHA(S%JiUD@{iF!Fs7d69^NK!&mn0UqfGglF=%KcsKivx-Nz1&A8-B0m(7@5v}^ zD~X!HL$==3bYeCYY)dL-c;ABv$@Taf+=G895qI7@L^{cvbmCpqj^AWc)L_#WSvH$oYDn_6 zPT{L8PiL)G3)%E_L8rg2u73ad>et`+ZLU^T#d-xYMB<{b*l`zfPs_f&i}g%DR|Z z85Y&PkoZZei7C|h8H#veQtkSEh)J4NcU~4#H}C*P(kY%Ncz=*?=Fr+X#1v(d_rBl8 zyiMAd(fe0FZRp=7?;^X4pp6`%R;PFoDL3{xX8HX(`&W9bjgIIpbBMIYf2m@8BI(QI zWkaO-PZS1)wA&n&t1*J zvAB*3>pg|=5v{}~Bg`w*8WBq$Yn~hXU!W6G3WsW4I*Y5It(^_cD$1n0sJ12