From 3dd27a1dd6c1f05b0931ec47fc430ab60e0a87e7 Mon Sep 17 00:00:00 2001 From: m-holger Date: Sun, 7 Sep 2025 14:53:01 +0100 Subject: [PATCH] Detect and warn about outline loops during structure traversal in `--check`. Update tests and adjust exit status accordingly. --- libqpdf/QPDFOutlineDocumentHelper.cc | 6 +++++- libqpdf/QPDFOutlineObjectHelper.cc | 9 +++++++-- qpdf/qtest/outlines.test | 2 +- qpdf/qtest/qpdf/outlines-with-loop-check.out | 5 +++-- qpdf/qtest/qpdf/outlines-with-loop.out | 2 ++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc index 55cd9ee..5403f64 100644 --- a/libqpdf/QPDFOutlineDocumentHelper.cc +++ b/libqpdf/QPDFOutlineDocumentHelper.cc @@ -53,7 +53,11 @@ QPDFOutlineDocumentHelper::validate(bool repair) } QPDFObjectHandle cur = outlines.getKey("/First"); QPDFObjGen::set seen; - while (!cur.null() && seen.add(cur)) { + while (!cur.null()) { + if (!seen.add(cur)) { + cur.warn("Loop detected loop in /Outlines tree"); + return; + } m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); cur = cur.getKey("/Next"); } diff --git a/libqpdf/QPDFOutlineObjectHelper.cc b/libqpdf/QPDFOutlineObjectHelper.cc index 0ae818e..3a163b6 100644 --- a/libqpdf/QPDFOutlineObjectHelper.cc +++ b/libqpdf/QPDFOutlineObjectHelper.cc @@ -20,15 +20,20 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( return; } if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) { + a_oh.warn("Loop detected loop in /Outlines tree"); return; } QPDFObjGen::set children; QPDFObjectHandle cur = a_oh.getKey("/First"); - while (!cur.null() && cur.isIndirect() && children.add(cur)) { + while (!cur.null() && cur.isIndirect()) { + if (!children.add(cur)) { + cur.warn("Loop detected loop in /Outlines tree"); + break; + } QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth); new_ooh.m->parent = std::make_shared(*this); - m->kids.push_back(new_ooh); + m->kids.emplace_back(new_ooh); cur = cur.getKey("/Next"); } } diff --git a/qpdf/qtest/outlines.test b/qpdf/qtest/outlines.test index 9c6b453..0e286da 100644 --- a/qpdf/qtest/outlines.test +++ b/qpdf/qtest/outlines.test @@ -32,7 +32,7 @@ foreach my $f (@outline_files) $td->runtest("outlines: outlines-with-loop --check", {$td->COMMAND => "qpdf --check outlines-with-loop.pdf"}, - {$td->FILE => "outlines-with-loop-check.out", $td->EXIT_STATUS => 0}, + {$td->FILE => "outlines-with-loop-check.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); cleanup(); diff --git a/qpdf/qtest/qpdf/outlines-with-loop-check.out b/qpdf/qtest/qpdf/outlines-with-loop-check.out index a38d385..d7648a2 100644 --- a/qpdf/qtest/qpdf/outlines-with-loop-check.out +++ b/qpdf/qtest/qpdf/outlines-with-loop-check.out @@ -2,5 +2,6 @@ checking outlines-with-loop.pdf PDF Version: 1.3 File is not encrypted File is not linearized -No syntax or stream encoding errors found; the file may still contain -errors that qpdf cannot detect +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/outlines-with-loop.out b/qpdf/qtest/qpdf/outlines-with-loop.out index 9ca99dd..e4f66f8 100644 --- a/qpdf/qtest/qpdf/outlines-with-loop.out +++ b/qpdf/qtest/qpdf/outlines-with-loop.out @@ -1,3 +1,5 @@ +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] page 11: Mern 1.1 -> 11: /Fit -> [ 17 0 R /Fit ] -- libgit2 0.21.4