Skip to content

Commit f581e31

Browse files
JonPReillypavel-alay
authored andcommitted
Construct PdfOutline iteratively
Prevents stackoverflow from occuring for documents with a large amount of bookmarks. DEVSIX-1707 Autoported commit. Original commit hash: [8c986f14e]
1 parent f1e3811 commit f581e31

File tree

6 files changed

+124
-16
lines changed

6 files changed

+124
-16
lines changed

itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfOutlineTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,5 +374,18 @@ public virtual void AddOutlinesWithNamedDestinations02() {
374374
NUnit.Framework.Assert.IsNull(new CompareTool().CompareByContent(filename, sourceFolder + "cmp_outlinesWithNamedDestinations02.pdf"
375375
, destinationFolder, "diff_"));
376376
}
377+
378+
/// <exception cref="System.IO.IOException"/>
379+
[NUnit.Framework.Test]
380+
public virtual void OutlineStackOverflowTest01() {
381+
PdfReader reader = new PdfReader(sourceFolder + "outlineStackOverflowTest01.pdf");
382+
PdfDocument pdfDoc = new PdfDocument(reader);
383+
try {
384+
pdfDoc.GetOutlines(true);
385+
}
386+
catch (StackOverflowException) {
387+
NUnit.Framework.Assert.Fail("StackOverflow thrown when reading document with a large number of outlines.");
388+
}
389+
}
377390
}
378391
}

itext/itext.io/Properties/AssemblyInfo.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
"75bcdbcecb7caf1f0f4b6e7d013906ba60b66eb1c8298e4efb052caf6cece4bf1816" +
2525
"902cc")]
2626

27+
[assembly: InternalsVisibleTo("itext.layout,PublicKey=0024000004800000940000000602000000240000525" +
28+
"3413100040000010001008b21ed5b3fc1c11996390981fe22bbe71a39a9e11d3c2ce" +
29+
"fddd6ee92920fa871f9666ae0fa941af0280d0653df048ae2d93f8c5e2d820dba3c8" +
30+
"df9ed468c8be40a6fffeb32aa481a254f0fb9f37aa7c3ec1c0acd2c009746bbdafcb" +
31+
"75bcdbcecb7caf1f0f4b6e7d013906ba60b66eb1c8298e4efb052caf6cece4bf1816" +
32+
"902cc")]
33+
2734
[assembly: ComVisible(false)]
2835

2936
[assembly: Guid("39631ecb-1d39-4eb2-b775-37bd34cbf5a4")]

itext/itext.kernel/itext/kernel/pdf/PdfCatalog.cs

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,7 @@ internal virtual PdfOutline GetOutlines(bool updateOutlines) {
394394
outlines = new PdfOutline(GetDocument());
395395
}
396396
else {
397-
outlines = new PdfOutline(OutlineRoot, outlineRoot, GetDocument());
398-
GetNextItem(outlineRoot.GetAsDictionary(PdfName.First), outlines, destsTree.GetNames());
397+
ConstructOutlines(outlineRoot, destsTree.GetNames());
399398
}
400399
return outlines;
401400
}
@@ -519,11 +518,70 @@ private void AddOutlineToPage(PdfOutline outline, IDictionary<String, PdfObject>
519518
}
520519
}
521520

522-
private void GetNextItem(PdfDictionary item, PdfOutline parent, IDictionary<String, PdfObject> names) {
523-
if (null == item) {
524-
return;
521+
/// <summary>Get the next outline of the current node in the outline tree by looking for a child or sibling node.
522+
/// </summary>
523+
/// <remarks>
524+
/// Get the next outline of the current node in the outline tree by looking for a child or sibling node.
525+
/// If there is no child or sibling of the current node
526+
/// <see cref="GetParentNextOutline(PdfDictionary)"/>
527+
/// is called to get a hierarchical parent's next node.
528+
/// <see langword="null"/>
529+
/// is returned if one does not exist.
530+
/// </remarks>
531+
/// <returns>
532+
/// the
533+
/// <see cref="PdfDictionary"/>
534+
/// object of the next outline if one exists,
535+
/// <see langword="null"/>
536+
/// otherwise.
537+
/// </returns>
538+
private PdfDictionary GetNextOutline(PdfDictionary first, PdfDictionary next, PdfDictionary parent) {
539+
if (first != null) {
540+
return first;
541+
}
542+
else {
543+
if (next != null) {
544+
return next;
545+
}
546+
else {
547+
return GetParentNextOutline(parent);
548+
}
549+
}
550+
}
551+
552+
/// <summary>Gets the parent's next outline of the current node.</summary>
553+
/// <remarks>
554+
/// Gets the parent's next outline of the current node.
555+
/// If the parent does not have a next we look at the grand parent, great-grand parent, etc until we find a next node or reach the root at which point
556+
/// <see langword="null"/>
557+
/// is returned to signify there is no next node present.
558+
/// </remarks>
559+
/// <returns>
560+
/// the
561+
/// <see cref="PdfDictionary"/>
562+
/// object of the next outline if one exists,
563+
/// <see langword="null"/>
564+
/// otherwise.
565+
/// </returns>
566+
private PdfDictionary GetParentNextOutline(PdfDictionary parent) {
567+
if (parent == null) {
568+
return null;
525569
}
526-
PdfOutline outline = new PdfOutline(item.GetAsString(PdfName.Title).ToUnicodeString(), item, parent);
570+
PdfDictionary current = null;
571+
while (current == null) {
572+
current = parent.GetAsDictionary(PdfName.Next);
573+
if (current == null) {
574+
parent = parent.GetAsDictionary(PdfName.Parent);
575+
if (parent == null) {
576+
return null;
577+
}
578+
}
579+
}
580+
return current;
581+
}
582+
583+
private void AddOutlineToPage(PdfOutline outline, PdfDictionary item, IDictionary<String, PdfObject> names
584+
) {
527585
PdfObject dest = item.Get(PdfName.Dest);
528586
if (dest != null) {
529587
PdfDestination destination = PdfDestination.MakeDestination(dest);
@@ -535,7 +593,7 @@ private void GetNextItem(PdfDictionary item, PdfOutline parent, IDictionary<Stri
535593
PdfDictionary action = item.GetAsDictionary(PdfName.A);
536594
if (action != null) {
537595
PdfName actionType = action.GetAsName(PdfName.S);
538-
//Check if it a go to action
596+
//Check if it is a go to action
539597
if (PdfName.GoTo.Equals(actionType)) {
540598
//Retrieve destination if it is.
541599
PdfObject destObject = action.Get(PdfName.D);
@@ -548,14 +606,43 @@ private void GetNextItem(PdfDictionary item, PdfOutline parent, IDictionary<Stri
548606
}
549607
}
550608
}
551-
parent.GetAllChildren().Add(outline);
552-
PdfDictionary processItem = item.GetAsDictionary(PdfName.First);
553-
if (processItem != null) {
554-
GetNextItem(processItem, outline, names);
609+
}
610+
611+
/// <summary>
612+
/// Constructs
613+
/// <see cref="outlines"/>
614+
/// iteratively
615+
/// </summary>
616+
private void ConstructOutlines(PdfDictionary outlineRoot, IDictionary<String, PdfObject> names) {
617+
if (outlineRoot == null) {
618+
return;
555619
}
556-
processItem = item.GetAsDictionary(PdfName.Next);
557-
if (processItem != null) {
558-
GetNextItem(processItem, parent, names);
620+
PdfDictionary first = outlineRoot.GetAsDictionary(PdfName.First);
621+
PdfDictionary current = first;
622+
PdfDictionary next;
623+
PdfDictionary parent;
624+
Dictionary<PdfDictionary, PdfOutline> parentOutlineMap = new Dictionary<PdfDictionary, PdfOutline>();
625+
outlines = new PdfOutline(OutlineRoot, outlineRoot, GetDocument());
626+
PdfOutline parentOutline = outlines;
627+
parentOutlineMap.Put(outlineRoot, parentOutline);
628+
while (current != null) {
629+
first = current.GetAsDictionary(PdfName.First);
630+
next = current.GetAsDictionary(PdfName.Next);
631+
parent = current.GetAsDictionary(PdfName.Parent);
632+
parentOutline = parentOutlineMap.Get(parent);
633+
PdfOutline currentOutline = new PdfOutline(current.GetAsString(PdfName.Title).ToUnicodeString(), current,
634+
parentOutline);
635+
AddOutlineToPage(currentOutline, current, names);
636+
parentOutline.GetAllChildren().Add(currentOutline);
637+
if (first != null) {
638+
parentOutlineMap.Put(current, currentOutline);
639+
}
640+
else {
641+
if (current == parent.GetAsDictionary(PdfName.Last)) {
642+
parentOutlineMap.JRemove(parent);
643+
}
644+
}
645+
current = GetNextOutline(first, next, parent);
559646
}
560647
}
561648
}

itext/itext.layout/itext/layout/LayoutExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ source product.
4848
using System.Linq;
4949
using System.Reflection;
5050
using System.Text;
51+
using iText.IO.Util.Collections;
5152

5253
namespace iText.Layout {
5354
internal static class LayoutExtensions {
@@ -216,7 +217,7 @@ public static Attribute GetCustomAttribute(this Assembly assembly, Type attribut
216217

217218
#if NETSTANDARD1_6
218219
public static MethodInfo GetMethod(this Type type, String methodName, Type[] parameterTypes) {
219-
return type.GetTypeInfo().GetMethod(methodName, parameterTypes);
220+
return type .GetTypeInfo().GetMethod(methodName, parameterTypes);
220221
}
221222

222223
public static ConstructorInfo GetConstructor(this Type type, Type[] parameterTypes) {

port-hash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4e4cfb45170aab18c72236302590b5533d50f69d
1+
8c986f14e6ff65251affa952cb41d17ba245ad6c

0 commit comments

Comments
 (0)