From 5821e489ebe1972695e211c05163609f75220c6d Mon Sep 17 00:00:00 2001 From: Edi Weissmann Date: Wed, 18 Oct 2023 14:24:20 +0200 Subject: [PATCH] close #165: StackOverflowError in PDPageTree.getInheritableAttribute --- .../org/sejda/sambox/pdmodel/PDPageTree.java | 16 ++++++- .../org/sejda/sambox/util/ObjectIdUtils.java | 42 +++++++++++++++++++ .../sejda/sambox/pdmodel/PDPageTreeTest.java | 39 +++++++++++++++-- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/sejda/sambox/util/ObjectIdUtils.java diff --git a/src/main/java/org/sejda/sambox/pdmodel/PDPageTree.java b/src/main/java/org/sejda/sambox/pdmodel/PDPageTree.java index 4abb67492..79c80f398 100644 --- a/src/main/java/org/sejda/sambox/pdmodel/PDPageTree.java +++ b/src/main/java/org/sejda/sambox/pdmodel/PDPageTree.java @@ -44,6 +44,7 @@ import org.sejda.sambox.cos.COSName; import org.sejda.sambox.cos.COSNull; import org.sejda.sambox.cos.COSObjectable; +import org.sejda.sambox.util.ObjectIdUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -136,6 +137,11 @@ public static COSBase getInheritableAttribute(COSDictionary * @return COS value for the given key */ public static COSBase getInheritableAttribute(COSDictionary node, COSName key) + { + return getInheritableAttribute(node, key, new HashSet<>()); + } + + public static COSBase getInheritableAttribute(COSDictionary node, COSName key, Set visitedObjectIds) { COSBase value = node.getDictionaryObject(key); if (value != null) @@ -154,7 +160,15 @@ public static COSBase getInheritableAttribute(COSDictionary node, COSName key) if (parent != null) { - return getInheritableAttribute(parent, key); + String objId = ObjectIdUtils.getObjectIdOf(node); + if(!objId.isBlank() && visitedObjectIds.contains(objId)) + { + // prevent infinite recursion + return null; + } + + visitedObjectIds.add(objId); + return getInheritableAttribute(parent, key, visitedObjectIds); } return null; diff --git a/src/main/java/org/sejda/sambox/util/ObjectIdUtils.java b/src/main/java/org/sejda/sambox/util/ObjectIdUtils.java new file mode 100644 index 000000000..b012bb5c8 --- /dev/null +++ b/src/main/java/org/sejda/sambox/util/ObjectIdUtils.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sejda.sambox.util; + +import org.sejda.sambox.cos.COSObjectKey; +import org.sejda.sambox.cos.COSObjectable; + +public class ObjectIdUtils { + private ObjectIdUtils() {} + + public static String getObjectIdOf(COSObjectable obj) + { + try + { + return getObjectIdOf(obj.getCOSObject().id().objectIdentifier); + } + catch (Exception ex) + { + return ""; + } + } + + public static String getObjectIdOf(COSObjectKey ident) + { + String gen = ident.generation() == 0 ? "" : ident.generation() + ""; + return ident.objectNumber() + "R" + gen; + } +} diff --git a/src/test/java/org/sejda/sambox/pdmodel/PDPageTreeTest.java b/src/test/java/org/sejda/sambox/pdmodel/PDPageTreeTest.java index 4ecfb9c0f..0a14709ff 100644 --- a/src/test/java/org/sejda/sambox/pdmodel/PDPageTreeTest.java +++ b/src/test/java/org/sejda/sambox/pdmodel/PDPageTreeTest.java @@ -19,16 +19,17 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.sejda.sambox.input.PDFParser.parse; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import org.junit.Test; import org.sejda.io.SeekableSources; +import org.sejda.sambox.cos.COSBase; +import org.sejda.sambox.cos.COSDictionary; import org.sejda.sambox.cos.COSName; import org.sejda.sambox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.sejda.sambox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; @@ -336,4 +337,36 @@ public void noStackOverflowInCaseOfLoop() throws IOException doc.getPage(0); } } + + @Test + public void getInheritableAttribute_recursive() throws IOException { + PDDocument doc = new PDDocument(); + PDPage page = new PDPage(); + + doc.addPage(page); + + COSDictionary dic1 = page.getCOSObject(); + COSDictionary dic2 = new COSDictionary(); + COSDictionary dic3 = new COSDictionary(); + + dic1.setItem(COSName.PARENT, dic2); + dic2.setItem(COSName.PARENT, dic3); + dic3.setItem(COSName.PARENT, dic1); + + File tempFile = Files.createTempFile("sambox_getInheritableAttribute_recursive", ".pdf").toFile(); + tempFile.deleteOnExit(); + + doc.save(tempFile.getAbsolutePath()); + + try (PDDocument doc2 = parse(SeekableSources.seekableSourceFrom(tempFile))) + { + PDPage p = doc.getPage(0); + COSBase value = PDPageTree.getInheritableAttribute(p.getCOSObject(), COSName.D); + assertNull(value); + } + finally + { + tempFile.delete(); + } + } }