Skip to content

Commit b57af5f

Browse files
BlackEgoistEgor Martsynkovsky
authored andcommitted
Fix problem with infinite loop occurs
Add flexibily to PdfCatalog#constructOutlines logic relying on PdfReader#StrictnessLevel Unignore some tests relying to problems with infinite loop in outlines while merging DEVSIX-3703
1 parent 31f8c23 commit b57af5f

File tree

8 files changed

+327
-72
lines changed

8 files changed

+327
-72
lines changed

kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ public final class KernelExceptionMessageConstant {
122122
public static final String CONTENT_STREAM_MUST_NOT_INVOKE_OPERATORS_THAT_SPECIFY_COLORS_OR_OTHER_COLOR_RELATED_PARAMETERS =
123123
"Content stream must not invoke operators that specify colors or other color related parameters in "
124124
+ "the graphics state.";
125+
public static final String CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP =
126+
"Document outline dictionary is corrupted: some outline (PDF object: \"{0}\") has wrong first/next link "
127+
+ "entry.";
125128
public static final String CORRUPTED_OUTLINE_NO_PARENT_ENTRY =
126129
"Document outline is corrupted: some outline (PDF object: \"{0}\") lacks the required parent entry.";
127130
public static final String CORRUPTED_OUTLINE_NO_TITLE_ENTRY =

kernel/src/main/java/com/itextpdf/kernel/logs/KernelLogMessageConstant.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ This file is part of the iText (R) project.
4848
*/
4949
public final class KernelLogMessageConstant {
5050

51+
public static final String CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP =
52+
"Document outline dictionary is corrupted: some outline (PDF object: \"{0}\") has wrong first/next link "
53+
+ "entry. Next outlines in this dictionary will be unprocessed.";
54+
5155
public static final String DCTDECODE_FILTER_DECODING =
5256
"DCTDecode filter decoding into the bit map is not supported. The stream data would be left in JPEG "
5357
+ "baseline format";

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfCatalog.java

Lines changed: 88 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ This file is part of the iText (R) project.
4747
import com.itextpdf.commons.utils.MessageFormatUtil;
4848
import com.itextpdf.kernel.exceptions.PdfException;
4949
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
50+
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
51+
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
5052
import com.itextpdf.kernel.pdf.action.PdfAction;
5153
import com.itextpdf.kernel.pdf.collection.PdfCollection;
5254
import com.itextpdf.kernel.pdf.layer.PdfOCProperties;
@@ -91,7 +93,7 @@ public class PdfCatalog extends PdfObjectWrapper<PdfDictionary> {
9193
*/
9294
protected PdfOCProperties ocProperties;
9395

94-
private static final String OutlineRoot = "Outlines";
96+
private static final String ROOT_OUTLINE_TITLE = "Outlines";
9597

9698
private PdfOutline outlines;
9799

@@ -561,6 +563,91 @@ void addRootOutline(PdfOutline outline) {
561563
}
562564
}
563565

566+
/**
567+
* Construct {@link PdfCatalog dictionary} iteratively. Invalid pdf documents will be processed depending on {@link
568+
* StrictnessLevel}, if it set to lenient, we will ignore and process invalid outline structure, otherwise {@link
569+
* PdfException} will be thrown.
570+
*
571+
* @param outlineRoot {@link PdfOutline dictionary} root.
572+
* @param names map containing the PdfObjects stored in the tree.
573+
*/
574+
void constructOutlines(PdfDictionary outlineRoot, Map<String, PdfObject> names) {
575+
if (outlineRoot == null) {
576+
return;
577+
}
578+
579+
PdfReader reader = getDocument().getReader();
580+
final boolean isLenientLevel =
581+
reader == null || StrictnessLevel.CONSERVATIVE.isStricter(reader.getStrictnessLevel());
582+
PdfDictionary current = outlineRoot.getAsDictionary(PdfName.First);
583+
584+
outlines = new PdfOutline(ROOT_OUTLINE_TITLE, outlineRoot, getDocument());
585+
PdfOutline parentOutline = outlines;
586+
587+
Map<PdfOutline, PdfDictionary> nextUnprocessedChildForParentMap = new HashMap<>();
588+
Set<PdfDictionary> alreadyVisitedOutlinesSet = new HashSet<>();
589+
590+
while (current != null) {
591+
PdfDictionary parent = current.getAsDictionary(PdfName.Parent);
592+
if (null == parent && !isLenientLevel) {
593+
throw new PdfException(
594+
MessageFormatUtil.format(
595+
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_NO_PARENT_ENTRY,
596+
current.indirectReference));
597+
}
598+
PdfString title = current.getAsString(PdfName.Title);
599+
if (null == title) {
600+
throw new PdfException(
601+
MessageFormatUtil.format(
602+
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_NO_TITLE_ENTRY,
603+
current.indirectReference));
604+
}
605+
PdfOutline currentOutline = new PdfOutline(title.toUnicodeString(), current, parentOutline);
606+
alreadyVisitedOutlinesSet.add(current);
607+
addOutlineToPage(currentOutline, current, names);
608+
parentOutline.getAllChildren().add(currentOutline);
609+
610+
PdfDictionary first = current.getAsDictionary(PdfName.First);
611+
PdfDictionary next = current.getAsDictionary(PdfName.Next);
612+
if (first != null) {
613+
if (alreadyVisitedOutlinesSet.contains(first)) {
614+
if (!isLenientLevel) {
615+
throw new PdfException(MessageFormatUtil.format(
616+
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP, first));
617+
}
618+
LOGGER.warn(MessageFormatUtil.format(
619+
KernelLogMessageConstant.CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP, first));
620+
return;
621+
}
622+
// Down in hierarchy; when returning up, process `next`.
623+
nextUnprocessedChildForParentMap.put(parentOutline, next);
624+
parentOutline = currentOutline;
625+
current = first;
626+
} else if (next != null) {
627+
if (alreadyVisitedOutlinesSet.contains(next)) {
628+
if (!isLenientLevel) {
629+
throw new PdfException(MessageFormatUtil.format(
630+
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP, next));
631+
}
632+
LOGGER.warn(MessageFormatUtil.format(
633+
KernelLogMessageConstant.CORRUPTED_OUTLINE_DICTIONARY_HAS_INFINITE_LOOP, next));
634+
return;
635+
}
636+
// Next sibling in hierarchy
637+
current = next;
638+
} else {
639+
// Up in hierarchy using 'nextUnprocessedChildForParentMap'.
640+
current = null;
641+
while (current == null && parentOutline != null) {
642+
parentOutline = parentOutline.getParent();
643+
if (parentOutline != null) {
644+
current = nextUnprocessedChildForParentMap.get(parentOutline);
645+
}
646+
}
647+
}
648+
}
649+
}
650+
564651
PdfDestination copyDestination(PdfObject dest, Map<PdfPage, PdfPage> page2page, PdfDocument toDocument) {
565652
if (null == dest) {
566653
return null;
@@ -683,61 +770,4 @@ private void addOutlineToPage(PdfOutline outline, PdfDictionary item, Map<String
683770
}
684771
}
685772
}
686-
687-
/**
688-
* Constructs {@link PdfCatalog#outlines} iteratively
689-
*/
690-
void constructOutlines(PdfDictionary outlineRoot, Map<String, PdfObject> names) {
691-
if (outlineRoot == null) {
692-
return;
693-
}
694-
PdfDictionary current = outlineRoot.getAsDictionary(PdfName.First);
695-
696-
outlines = new PdfOutline(OutlineRoot, outlineRoot, getDocument());
697-
PdfOutline parentOutline = outlines;
698-
699-
// map `PdfOutline` to the next sibling to process in the hierarchy
700-
HashMap<PdfOutline, PdfDictionary> positionMap = new HashMap<>();
701-
702-
while (current != null) {
703-
PdfDictionary parent = current.getAsDictionary(PdfName.Parent);
704-
if (null == parent) {
705-
throw new PdfException(
706-
MessageFormatUtil.format(
707-
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_NO_PARENT_ENTRY,
708-
current.indirectReference));
709-
}
710-
PdfString title = current.getAsString(PdfName.Title);
711-
if (null == title) {
712-
throw new PdfException(
713-
MessageFormatUtil.format(
714-
KernelExceptionMessageConstant.CORRUPTED_OUTLINE_NO_TITLE_ENTRY,
715-
current.indirectReference));
716-
}
717-
PdfOutline currentOutline = new PdfOutline(title.toUnicodeString(), current, parentOutline);
718-
addOutlineToPage(currentOutline, current, names);
719-
parentOutline.getAllChildren().add(currentOutline);
720-
721-
PdfDictionary first = current.getAsDictionary(PdfName.First);
722-
PdfDictionary next = current.getAsDictionary(PdfName.Next);
723-
if (first != null) {
724-
// Down in hierarchy; when returning up, process `next`
725-
positionMap.put(parentOutline, next);
726-
parentOutline = currentOutline;
727-
current = first;
728-
} else if (next != null) {
729-
// Next sibling in hierarchy
730-
current = next;
731-
} else {
732-
// Up in hierarchy using `positionMap`
733-
current = null;
734-
while (current == null && parentOutline != null) {
735-
parentOutline = parentOutline.getParent();
736-
if (parentOutline != null) {
737-
current = positionMap.get(parentOutline);
738-
}
739-
}
740-
}
741-
}
742-
}
743773
}

0 commit comments

Comments
 (0)