Skip to content

Commit fea9a1a

Browse files
yulian-gaponenkoiText-CI
authored andcommitted
Fix smart-mode serialization content cluttering by internal buffer out-of-range bytes
DEVSIX-2883
1 parent b50863a commit fea9a1a

File tree

9 files changed

+223
-8
lines changed

9 files changed

+223
-8
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,8 +1265,10 @@ public List<PdfPage> copyPagesTo(List<Integer> pagesToCopy, PdfDocument toDocume
12651265

12661266
/**
12671267
* Flush all copied objects and remove them from copied cache.
1268-
* Note, if you will copy objects from the same document, doublicated objects will be created.
1269-
*
1268+
* <p>
1269+
* Note, if you will copy objects from the same document, duplicated objects will be created.
1270+
* That's why usually this method is meant to be used when all copying from source document is finished.
1271+
* For other cases one can also consider other flushing mechanisms, e.g. pages-based flushing.
12701272
* @param sourceDoc source document
12711273
*/
12721274
public void flushCopiedObjects(PdfDocument sourceDoc) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,9 @@ protected PdfObject copyObject(PdfObject obj, PdfDocument documentTo, boolean al
347347
copiedObjectKey = new PdfDocument.IndirectRefDescription(indirectReference);
348348

349349
PdfIndirectReference copiedIndirectReference = copiedObjects.get(copiedObjectKey);
350-
if (copiedIndirectReference != null)
350+
if (copiedIndirectReference != null) {
351351
return copiedIndirectReference.getRefersTo();
352+
}
352353
}
353354

354355
SerializedObjectContent serializedContent = null;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.io.source.ByteBuffer;
4646
import com.itextpdf.kernel.PdfException;
47-
4847
import java.io.Serializable;
4948
import java.security.MessageDigest;
5049
import java.util.HashMap;
@@ -98,8 +97,6 @@ public SerializedObjectContent serializeObject(PdfObject obj) {
9897
return new SerializedObjectContent(content);
9998
}
10099

101-
private static class SelfReferenceException extends Exception{}
102-
103100
private void serObject(PdfObject obj, ByteBuffer bb, int level, Map<PdfIndirectReference, byte[]> serializedCache) throws SelfReferenceException {
104101
if (level <= 0) {
105102
return;
@@ -121,7 +118,7 @@ private void serObject(PdfObject obj, ByteBuffer bb, int level, Map<PdfIndirectR
121118

122119
if (serializedCache.keySet().contains(reference)) {
123120
//referencing itself
124-
throw new SelfReferenceException();
121+
throw new SelfReferenceException();
125122
}
126123
serializedCache.put(reference, null);
127124

@@ -151,7 +148,7 @@ private void serObject(PdfObject obj, ByteBuffer bb, int level, Map<PdfIndirectR
151148

152149
if (savedBb != null) {
153150
serializedCache.put(reference, bb.toByteArray());
154-
savedBb.append(bb.getInternalBuffer());
151+
savedBb.append(bb.getInternalBuffer(), 0, bb.size());
155152
}
156153
}
157154

@@ -188,4 +185,7 @@ private boolean isKeyRefersBack(PdfDictionary dic, PdfName key) {
188185
return key.equals(PdfName.P) && (dic.get(key).isIndirectReference() || dic.get(key).isDictionary())
189186
|| key.equals(PdfName.Parent);
190187
}
188+
189+
private static class SelfReferenceException extends Exception {
190+
}
191191
}

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfCopyTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ This file is part of the iText (R) project.
4343
package com.itextpdf.kernel.pdf;
4444

4545
import com.itextpdf.io.LogMessageConstant;
46+
import com.itextpdf.io.source.ByteArrayOutputStream;
4647
import com.itextpdf.io.source.ByteUtils;
4748
import com.itextpdf.kernel.utils.CompareTool;
4849
import com.itextpdf.test.ExtendedITextTest;
4950
import com.itextpdf.test.annotations.LogMessage;
5051
import com.itextpdf.test.annotations.LogMessages;
5152
import com.itextpdf.test.annotations.type.IntegrationTest;
53+
import java.io.ByteArrayInputStream;
5254
import java.io.IOException;
5355
import java.util.Arrays;
5456
import org.junit.Assert;
@@ -253,4 +255,35 @@ public void copyIndirectInheritablePageEntriesTest01() throws IOException, Inter
253255
assertNull(new CompareTool().compareByContent(dest, cmp, destinationFolder, "diff_"));
254256
}
255257

258+
@Test
259+
public void copySelfContainedObject() throws IOException {
260+
ByteArrayOutputStream inputBytes = new ByteArrayOutputStream();
261+
PdfDocument prepInputDoc = new PdfDocument(new PdfWriter(inputBytes));
262+
PdfDictionary selfContainedDict = new PdfDictionary();
263+
PdfName randDictName = PdfName.Sound;
264+
PdfName randEntry1 = PdfName.R;
265+
PdfName randEntry2 = PdfName.S;
266+
selfContainedDict.put(randEntry1, selfContainedDict);
267+
selfContainedDict.put(randEntry2, selfContainedDict);
268+
prepInputDoc.addNewPage().put(randDictName, selfContainedDict.makeIndirect(prepInputDoc));
269+
prepInputDoc.close();
270+
271+
272+
PdfDocument srcDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(inputBytes.toByteArray())));
273+
PdfDocument destDoc = new PdfDocument(new PdfWriter(destinationFolder + "copySelfContainedObject.pdf"));
274+
275+
srcDoc.copyPagesTo(1, 1, destDoc);
276+
277+
PdfDictionary destPageObj = destDoc.getFirstPage().getPdfObject();
278+
PdfDictionary destSelfContainedDict = destPageObj.getAsDictionary(randDictName);
279+
PdfDictionary destSelfContainedDictR = destSelfContainedDict.getAsDictionary(randEntry1);
280+
PdfDictionary destSelfContainedDictS = destSelfContainedDict.getAsDictionary(randEntry2);
281+
282+
Assert.assertEquals(destSelfContainedDict.getIndirectReference(), destSelfContainedDictR.getIndirectReference());
283+
Assert.assertEquals(destSelfContainedDict.getIndirectReference(), destSelfContainedDictS.getIndirectReference());
284+
285+
destDoc.close();
286+
srcDoc.close();
287+
}
288+
256289
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.itextpdf.kernel.pdf;
2+
3+
import com.itextpdf.kernel.font.PdfFont;
4+
import com.itextpdf.kernel.font.PdfFontFactory;
5+
import com.itextpdf.kernel.geom.Rectangle;
6+
import com.itextpdf.kernel.pdf.action.PdfAction;
7+
import com.itextpdf.kernel.pdf.annot.PdfCircleAnnotation;
8+
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
9+
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
10+
import com.itextpdf.kernel.utils.CompareTool;
11+
import com.itextpdf.test.ExtendedITextTest;
12+
import com.itextpdf.test.annotations.type.IntegrationTest;
13+
import java.io.ByteArrayInputStream;
14+
import java.io.ByteArrayOutputStream;
15+
import java.io.IOException;
16+
import java.util.Map;
17+
import org.junit.Assert;
18+
import org.junit.BeforeClass;
19+
import org.junit.Test;
20+
import org.junit.experimental.categories.Category;
21+
22+
@Category(IntegrationTest.class)
23+
public class SmartModeTest extends ExtendedITextTest {
24+
25+
public static final String destinationFolder = "./target/test/com/itextpdf/kernel/pdf/SmartModeTest/";
26+
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/SmartModeTest/";
27+
28+
@BeforeClass
29+
public static void beforeClass() {
30+
createOrClearDestinationFolder(destinationFolder);
31+
}
32+
33+
@Test
34+
public void smartModeSameResourcesCopyingAndFlushing() throws IOException, InterruptedException {
35+
String outFile = destinationFolder + "smartModeSameResourcesCopyingAndFlushing.pdf";
36+
String cmpFile = sourceFolder + "cmp_smartModeSameResourcesCopyingAndFlushing.pdf";
37+
String[] srcFiles = new String[]{
38+
sourceFolder + "indirectResourcesStructure.pdf",
39+
sourceFolder + "indirectResourcesStructure2.pdf"
40+
};
41+
42+
PdfDocument outputDoc = new PdfDocument(new PdfWriter(outFile, new WriterProperties().useSmartMode()));
43+
44+
for (String srcFile : srcFiles) {
45+
PdfDocument sourceDoc = new PdfDocument(new PdfReader(srcFile));
46+
sourceDoc.copyPagesTo(1, sourceDoc.getNumberOfPages(), outputDoc);
47+
sourceDoc.close();
48+
49+
outputDoc.flushCopiedObjects(sourceDoc);
50+
}
51+
52+
outputDoc.close();
53+
54+
PdfDocument assertDoc = new PdfDocument(new PdfReader(outFile));
55+
PdfIndirectReference page1ResFontObj = assertDoc.getPage(1).getPdfObject().getAsDictionary(PdfName.Resources)
56+
.getAsDictionary(PdfName.Font).getIndirectReference();
57+
PdfIndirectReference page2ResFontObj = assertDoc.getPage(2).getPdfObject().getAsDictionary(PdfName.Resources)
58+
.getAsDictionary(PdfName.Font).getIndirectReference();
59+
PdfIndirectReference page3ResFontObj = assertDoc.getPage(3).getPdfObject().getAsDictionary(PdfName.Resources)
60+
.getAsDictionary(PdfName.Font).getIndirectReference();
61+
62+
Assert.assertTrue(page1ResFontObj.equals(page2ResFontObj));
63+
Assert.assertTrue(page1ResFontObj.equals(page3ResFontObj));
64+
assertDoc.close();
65+
66+
Assert.assertNull(new CompareTool().compareByContent(outFile, cmpFile, destinationFolder));
67+
}
68+
69+
@Test
70+
public void smartModeSameResourcesCopyingModifyingAndFlushing() throws IOException {
71+
String outFile = destinationFolder + "smartModeSameResourcesCopyingModifyingAndFlushing.pdf";
72+
String[] srcFiles = new String[]{
73+
sourceFolder + "indirectResourcesStructure.pdf",
74+
sourceFolder + "indirectResourcesStructure2.pdf"
75+
};
76+
boolean exceptionCaught = false;
77+
78+
PdfDocument outputDoc = new PdfDocument(new PdfWriter(outFile, new WriterProperties().useSmartMode()));
79+
80+
int lastPageNum = 1;
81+
PdfFont font = PdfFontFactory.createFont();
82+
for (String srcFile : srcFiles) {
83+
PdfDocument sourceDoc = new PdfDocument(new PdfReader(srcFile));
84+
sourceDoc.copyPagesTo(1, sourceDoc.getNumberOfPages(), outputDoc);
85+
sourceDoc.close();
86+
87+
int i;
88+
for (i = lastPageNum; i <= outputDoc.getNumberOfPages(); ++i) {
89+
PdfCanvas canvas;
90+
try {
91+
canvas = new PdfCanvas(outputDoc.getPage(i));
92+
} catch (NullPointerException expected) {
93+
// Smart mode makes it possible to share objects coming from different source documents.
94+
// Flushing one object documents might make it impossible to modify further copied objects.
95+
Assert.assertEquals(2, i);
96+
exceptionCaught = true;
97+
break;
98+
}
99+
canvas.beginText().moveText(36, 36).setFontAndSize(font, 12).showText("Page " + i).endText();
100+
}
101+
lastPageNum = i;
102+
103+
if (exceptionCaught) {
104+
break;
105+
}
106+
107+
outputDoc.flushCopiedObjects(sourceDoc);
108+
}
109+
110+
if (!exceptionCaught) {
111+
Assert.fail();
112+
}
113+
}
114+
115+
@Test
116+
public void smartModeSameResourcesCopyingModifyingAndFlushing_ensureObjectFresh() throws IOException, InterruptedException {
117+
String outFile = destinationFolder + "smartModeSameResourcesCopyingModifyingAndFlushing_ensureObjectFresh.pdf";
118+
String cmpFile = sourceFolder + "cmp_smartModeSameResourcesCopyingModifyingAndFlushing_ensureObjectFresh.pdf";
119+
String[] srcFiles = new String[]{
120+
sourceFolder + "indirectResourcesStructure.pdf",
121+
sourceFolder + "indirectResourcesStructure2.pdf"
122+
};
123+
124+
PdfDocument outputDoc = new PdfDocument(new PdfWriter(outFile, new WriterProperties().useSmartMode()));
125+
126+
int lastPageNum = 1;
127+
PdfFont font = PdfFontFactory.createFont();
128+
for (String srcFile : srcFiles) {
129+
PdfDocument sourceDoc = new PdfDocument(new PdfReader(srcFile));
130+
for (int i = 1; i <= sourceDoc.getNumberOfPages(); ++i) {
131+
PdfDictionary srcRes = sourceDoc.getPage(i).getPdfObject().getAsDictionary(PdfName.Resources);
132+
133+
// Ensures that objects copied to the output document are fresh,
134+
// i.e. are not reused from already copied objects cache.
135+
boolean ensureObjectIsFresh = true;
136+
// it's crucial to copy first inner objects and then the container object!
137+
for (PdfObject v : srcRes.values()) {
138+
if (v.getIndirectReference() != null) {
139+
// We are not interested in returned copied objects instances, they will be picked up by
140+
// general copying mechanism from copied objects cache by default.
141+
v.copyTo(outputDoc, ensureObjectIsFresh);
142+
}
143+
}
144+
if (srcRes.getIndirectReference() != null) {
145+
srcRes.copyTo(outputDoc, ensureObjectIsFresh);
146+
}
147+
}
148+
sourceDoc.copyPagesTo(1, sourceDoc.getNumberOfPages(), outputDoc);
149+
sourceDoc.close();
150+
151+
int i;
152+
for (i = lastPageNum; i <= outputDoc.getNumberOfPages(); ++i) {
153+
PdfPage page = outputDoc.getPage(i);
154+
PdfCanvas canvas = new PdfCanvas(page);
155+
canvas.beginText().moveText(36, 36).setFontAndSize(font, 12).showText("Page " + i).endText();
156+
}
157+
lastPageNum = i;
158+
159+
outputDoc.flushCopiedObjects(sourceDoc);
160+
}
161+
162+
outputDoc.close();
163+
164+
PdfDocument assertDoc = new PdfDocument(new PdfReader(outFile));
165+
PdfIndirectReference page1ResFontObj = assertDoc.getPage(1).getPdfObject().getAsDictionary(PdfName.Resources)
166+
.getAsDictionary(PdfName.Font).getIndirectReference();
167+
PdfIndirectReference page2ResFontObj = assertDoc.getPage(2).getPdfObject().getAsDictionary(PdfName.Resources)
168+
.getAsDictionary(PdfName.Font).getIndirectReference();
169+
PdfIndirectReference page3ResFontObj = assertDoc.getPage(3).getPdfObject().getAsDictionary(PdfName.Resources)
170+
.getAsDictionary(PdfName.Font).getIndirectReference();
171+
172+
Assert.assertFalse(page1ResFontObj.equals(page2ResFontObj));
173+
Assert.assertFalse(page1ResFontObj.equals(page3ResFontObj));
174+
Assert.assertFalse(page2ResFontObj.equals(page3ResFontObj));
175+
assertDoc.close();
176+
177+
Assert.assertNull(new CompareTool().compareByContent(outFile, cmpFile, destinationFolder));
178+
}
179+
}

0 commit comments

Comments
 (0)