Commit 3dd27a1dd6c1f05b0931ec47fc430ab60e0a87e7

Authored by m-holger
1 parent 206c2fc1

Detect and warn about outline loops during structure traversal in `--check`. Upd…

…ate tests and adjust exit status accordingly.
libqpdf/QPDFOutlineDocumentHelper.cc
@@ -53,7 +53,11 @@ QPDFOutlineDocumentHelper::validate(bool repair) @@ -53,7 +53,11 @@ QPDFOutlineDocumentHelper::validate(bool repair)
53 } 53 }
54 QPDFObjectHandle cur = outlines.getKey("/First"); 54 QPDFObjectHandle cur = outlines.getKey("/First");
55 QPDFObjGen::set seen; 55 QPDFObjGen::set seen;
56 - while (!cur.null() && seen.add(cur)) { 56 + while (!cur.null()) {
  57 + if (!seen.add(cur)) {
  58 + cur.warn("Loop detected loop in /Outlines tree");
  59 + return;
  60 + }
57 m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); 61 m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
58 cur = cur.getKey("/Next"); 62 cur = cur.getKey("/Next");
59 } 63 }
libqpdf/QPDFOutlineObjectHelper.cc
@@ -20,15 +20,20 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( @@ -20,15 +20,20 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper(
20 return; 20 return;
21 } 21 }
22 if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) { 22 if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) {
  23 + a_oh.warn("Loop detected loop in /Outlines tree");
23 return; 24 return;
24 } 25 }
25 26
26 QPDFObjGen::set children; 27 QPDFObjGen::set children;
27 QPDFObjectHandle cur = a_oh.getKey("/First"); 28 QPDFObjectHandle cur = a_oh.getKey("/First");
28 - while (!cur.null() && cur.isIndirect() && children.add(cur)) { 29 + while (!cur.null() && cur.isIndirect()) {
  30 + if (!children.add(cur)) {
  31 + cur.warn("Loop detected loop in /Outlines tree");
  32 + break;
  33 + }
29 QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth); 34 QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth);
30 new_ooh.m->parent = std::make_shared<QPDFOutlineObjectHelper>(*this); 35 new_ooh.m->parent = std::make_shared<QPDFOutlineObjectHelper>(*this);
31 - m->kids.push_back(new_ooh); 36 + m->kids.emplace_back(new_ooh);
32 cur = cur.getKey("/Next"); 37 cur = cur.getKey("/Next");
33 } 38 }
34 } 39 }
qpdf/qtest/outlines.test
@@ -32,7 +32,7 @@ foreach my $f (@outline_files) @@ -32,7 +32,7 @@ foreach my $f (@outline_files)
32 32
33 $td->runtest("outlines: outlines-with-loop --check", 33 $td->runtest("outlines: outlines-with-loop --check",
34 {$td->COMMAND => "qpdf --check outlines-with-loop.pdf"}, 34 {$td->COMMAND => "qpdf --check outlines-with-loop.pdf"},
35 - {$td->FILE => "outlines-with-loop-check.out", $td->EXIT_STATUS => 0}, 35 + {$td->FILE => "outlines-with-loop-check.out", $td->EXIT_STATUS => 3},
36 $td->NORMALIZE_NEWLINES); 36 $td->NORMALIZE_NEWLINES);
37 37
38 cleanup(); 38 cleanup();
qpdf/qtest/qpdf/outlines-with-loop-check.out
@@ -2,5 +2,6 @@ checking outlines-with-loop.pdf @@ -2,5 +2,6 @@ checking outlines-with-loop.pdf
2 PDF Version: 1.3 2 PDF Version: 1.3
3 File is not encrypted 3 File is not encrypted
4 File is not linearized 4 File is not linearized
5 -No syntax or stream encoding errors found; the file may still contain  
6 -errors that qpdf cannot detect 5 +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree
  6 +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree
  7 +qpdf: operation succeeded with warnings
qpdf/qtest/qpdf/outlines-with-loop.out
  1 +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree
  2 +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree
1 page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] 3 page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ]
2 page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] 4 page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ]
3 page 11: Mern 1.1 -> 11: /Fit -> [ 17 0 R /Fit ] 5 page 11: Mern 1.1 -> 11: /Fit -> [ 17 0 R /Fit ]