Skip to content

Commit ce23a8f

Browse files
committed
Introduce ForbidRelease and ReadOnly flags. Make PdfObject#release() less prone to errors
DEVSIX-480
1 parent 4489d12 commit ce23a8f

File tree

11 files changed

+145
-77
lines changed

11 files changed

+145
-77
lines changed

forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,8 @@ public PdfAcroForm(PdfDictionary pdfObject) {
108108
* @param fields a {@link PdfArray} of {@link PdfDictionary} objects
109109
*/
110110
public PdfAcroForm(PdfArray fields) {
111-
super(new PdfDictionary());
112-
put(PdfName.Fields, fields);
113-
getFormFields();
114-
xfaForm = new XfaForm();
111+
this(createAcroFormDictionaryByFields(fields));
112+
setForbidRelease();
115113
}
116114

117115
/**
@@ -986,6 +984,25 @@ public String getXfaFieldValue(String name) {
986984
return null;
987985
}
988986

987+
/**
988+
* Releases underlying pdf object and other pdf entities used by wrapper.
989+
* This method should be called instead of direct call to {@link PdfObject#release()} if the wrapper is used.
990+
*/
991+
public void release() {
992+
unsetForbidRelease();
993+
getPdfObject().release();
994+
for (PdfFormField field : fields.values()) {
995+
field.release();
996+
}
997+
fields = null;
998+
}
999+
1000+
private static PdfDictionary createAcroFormDictionaryByFields(PdfArray fields) {
1001+
PdfDictionary dictionary = new PdfDictionary();
1002+
dictionary.put(PdfName.Fields, fields);
1003+
return dictionary;
1004+
}
1005+
9891006
private PdfPage getFieldPage(PdfDictionary annotDic) {
9901007
PdfDictionary pageDic = annotDic.getAsDictionary(PdfName.P);
9911008
if (pageDic != null) {

forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public class PdfFormField extends PdfObjectWrapper<PdfDictionary> {
129129
public PdfFormField(PdfDictionary pdfObject) {
130130
super(pdfObject);
131131
ensureObjectIsAddedToDocument(pdfObject);
132+
setForbidRelease();
132133
}
133134

134135
/**
@@ -1787,6 +1788,64 @@ public <T extends PdfFormField> T setAppearance(PdfName appearanceType, String a
17871788
return (T) this;
17881789
}
17891790

1791+
/**
1792+
* Releases underlying pdf object and other pdf entities used by wrapper.
1793+
* This method should be called instead of direct call to {@link PdfObject#release()} if the wrapper is used.
1794+
*/
1795+
public void release() {
1796+
unsetForbidRelease();
1797+
getPdfObject().release();
1798+
}
1799+
1800+
protected static Object[] splitDAelements(String da) {
1801+
PdfTokenizer tk = new PdfTokenizer(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(PdfEncodings.convertToBytes(da, null))));
1802+
List<String> stack = new ArrayList<>();
1803+
Object ret[] = new Object[3];
1804+
try {
1805+
while (tk.nextToken()) {
1806+
if (tk.getTokenType() == PdfTokenizer.TokenType.Comment)
1807+
continue;
1808+
if (tk.getTokenType() == PdfTokenizer.TokenType.Other) {
1809+
String operator = tk.getStringValue();
1810+
if (operator.equals("Tf")) {
1811+
if (stack.size() >= 2) {
1812+
ret[DA_FONT] = stack.get(stack.size() - 2);
1813+
ret[DA_SIZE] = new Integer(stack.get(stack.size() - 1));
1814+
}
1815+
} else if (operator.equals("g")) {
1816+
if (stack.size() >= 1) {
1817+
float gray = new Float(stack.get(stack.size() - 1));
1818+
if (gray != 0) {
1819+
ret[DA_COLOR] = new DeviceGray(gray);
1820+
}
1821+
}
1822+
} else if (operator.equals("rg")) {
1823+
if (stack.size() >= 3) {
1824+
float red = new Float(stack.get(stack.size() - 3));
1825+
float green = new Float(stack.get(stack.size() - 2));
1826+
float blue = new Float(stack.get(stack.size() - 1));
1827+
ret[DA_COLOR] = new DeviceRgb(red, green, blue);
1828+
}
1829+
} else if (operator.equals("k")) {
1830+
if (stack.size() >= 4) {
1831+
float cyan = new Float(stack.get(stack.size() - 4));
1832+
float magenta = new Float(stack.get(stack.size() - 3));
1833+
float yellow = new Float(stack.get(stack.size() - 2));
1834+
float black = new Float(stack.get(stack.size() - 1));
1835+
ret[DA_COLOR] = new DeviceCmyk(cyan, magenta, yellow, black);
1836+
}
1837+
}
1838+
stack.clear();
1839+
} else {
1840+
stack.add(tk.getStringValue());
1841+
}
1842+
}
1843+
} catch (IOException e) {
1844+
1845+
}
1846+
return ret;
1847+
}
1848+
17901849
@Override
17911850
protected boolean isWrappedObjectMustBeIndirect() {
17921851
return true;
@@ -1887,55 +1946,6 @@ protected Object[] getFontAndSize(PdfDictionary asNormal) throws IOException {
18871946
return fontAndSize;
18881947
}
18891948

1890-
public static Object[] splitDAelements(String da) {
1891-
PdfTokenizer tk = new PdfTokenizer(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(PdfEncodings.convertToBytes(da, null))));
1892-
List<String> stack = new ArrayList<>();
1893-
Object ret[] = new Object[3];
1894-
try {
1895-
while (tk.nextToken()) {
1896-
if (tk.getTokenType() == PdfTokenizer.TokenType.Comment)
1897-
continue;
1898-
if (tk.getTokenType() == PdfTokenizer.TokenType.Other) {
1899-
String operator = tk.getStringValue();
1900-
if (operator.equals("Tf")) {
1901-
if (stack.size() >= 2) {
1902-
ret[DA_FONT] = stack.get(stack.size() - 2);
1903-
ret[DA_SIZE] = new Integer(stack.get(stack.size() - 1));
1904-
}
1905-
} else if (operator.equals("g")) {
1906-
if (stack.size() >= 1) {
1907-
float gray = new Float(stack.get(stack.size() - 1));
1908-
if (gray != 0) {
1909-
ret[DA_COLOR] = new DeviceGray(gray);
1910-
}
1911-
}
1912-
} else if (operator.equals("rg")) {
1913-
if (stack.size() >= 3) {
1914-
float red = new Float(stack.get(stack.size() - 3));
1915-
float green = new Float(stack.get(stack.size() - 2));
1916-
float blue = new Float(stack.get(stack.size() - 1));
1917-
ret[DA_COLOR] = new DeviceRgb(red, green, blue);
1918-
}
1919-
} else if (operator.equals("k")) {
1920-
if (stack.size() >= 4) {
1921-
float cyan = new Float(stack.get(stack.size() - 4));
1922-
float magenta = new Float(stack.get(stack.size() - 3));
1923-
float yellow = new Float(stack.get(stack.size() - 2));
1924-
float black = new Float(stack.get(stack.size() - 1));
1925-
ret[DA_COLOR] = new DeviceCmyk(cyan, magenta, yellow, black);
1926-
}
1927-
}
1928-
stack.clear();
1929-
} else {
1930-
stack.add(tk.getStringValue());
1931-
}
1932-
}
1933-
} catch (IOException e) {
1934-
1935-
}
1936-
return ret;
1937-
}
1938-
19391949
/**
19401950
* Draws the visual appearance of text in a form field.
19411951
*

kernel/src/main/java/com/itextpdf/kernel/PdfException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class PdfException extends RuntimeException {
4545
public static final String CannotSetDataToPdfstreamWhichWasCreatedByInputstream = "cannot.set.data.to.pdfstream.which.was.created.by.inputstream";
4646
public static final String CannotSplitDocumentThatIsBeingWritten = "cannot.split.document.that.is.being.written";
4747
public static final String CannotWritePdfStream = "cannot.write.pdf.stream";
48+
public static final String CannotWriteObjectAfterItWasReleased = "Cannot write object after it was released. In normal situation the object must be read once again before being written";
4849
public static final String CantDecodePkcs7SigneddataObject = "can.t.decode.pkcs7signeddata.object";
4950
public static final String CantFindSigningCertificateWithSerial1 = "can.t.find.signing.certificate.with.serial {0}";
5051
public static final String CfNotFoundEncryption = "cf.not.found.encryption";

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
import com.itextpdf.kernel.PdfException;
44
import com.itextpdf.kernel.pdf.action.PdfAction;
5-
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
6-
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
75
import com.itextpdf.kernel.pdf.collection.PdfCollection;
86
import com.itextpdf.kernel.pdf.layer.PdfOCProperties;
97
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
108
import com.itextpdf.kernel.pdf.navigation.PdfExplicitDestination;
119
import com.itextpdf.kernel.pdf.navigation.PdfStringDestination;
1210

13-
import java.util.*;
11+
import java.util.ArrayList;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
1415

1516
public class PdfCatalog extends PdfObjectWrapper<PdfDictionary> {
1617

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public PdfDocumentInfo(PdfDictionary pdfObject, PdfDocument pdfDocument) {
1111
if (pdfDocument.getWriter() != null) {
1212
this.getPdfObject().makeIndirect(pdfDocument);
1313
}
14+
setForbidRelease();
1415
}
1516

1617
public PdfDocumentInfo(PdfDictionary pdfObject) {

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

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.itextpdf.kernel.pdf;
22

33
import com.itextpdf.kernel.PdfException;
4+
45
import java.io.IOException;
56

7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
610
abstract public class PdfObject {
711

812
static public final byte Array = 1;
@@ -22,30 +26,38 @@ abstract public class PdfObject {
2226
protected PdfIndirectReference indirectReference = null;
2327

2428
// Indicates if the object has been flushed.
25-
protected static final byte Flushed = 1;
29+
protected static final short Flushed = 1;
2630
// Indicates that the indirect reference of the object could be reused or have to be marked as free.
27-
protected static final byte Free = 2;
31+
protected static final short Free = 2;
2832
// Indicates that definition of the indirect reference of the object still not found (e.g. keys in XRefStm).
29-
protected static final byte Reading = 4;
33+
protected static final short Reading = 4;
3034
// Indicates that object changed (using in stamp mode).
31-
protected static final byte Modified = 8;
35+
protected static final short Modified = 8;
3236
// Indicates that the indirect reference of the object represents ObjectStream from original document.
3337
// When PdfReader read ObjectStream reference marked as OriginalObjectStream
3438
// to avoid further reusing.
35-
protected static final byte OriginalObjectStream = 16;
39+
protected static final short OriginalObjectStream = 16;
3640
// For internal usage only. Marks objects that shall be written to the output document.
3741
// Option is needed to build the correct PDF objects tree when closing the document.
3842
// As a result it avoids writing unused (removed) objects.
39-
protected static final byte MustBeFlushed = 32;
43+
protected static final short MustBeFlushed = 32;
4044
// Indicates that the object shall be indirect when it is written to the document.
4145
// It is used to postpone the creation of indirect reference for the objects that shall be indirect,
4246
// so it is possible to create such objects without PdfDocument instance.
43-
protected static final byte MustBeIndirect = 64;
47+
protected static final short MustBeIndirect = 64;
48+
// Indicates that the object is highly sensitive and we do not want to release it even if release() is called.
49+
// This flag can be set in stamping mode in object wrapper constructors and is automatically set when setModified
50+
// flag is set (we do not want to release changed objects).
51+
// The flag is set automatically for some wrappers that need document even in reader mode (FormFields etc).
52+
protected static final short ForbidRelease = 128;
53+
// Indicates that we do not want this object to be ever written into the resultant document
54+
// (because of multiple objects read from the same reference inconsistency).
55+
protected static final short ReadOnly = 256;
4456

4557
/**
4658
* Indicate same special states of PdfIndirectObject or PdfObject like @see Free, @see Reading, @see Modified.
4759
*/
48-
private byte state;
60+
private short state;
4961

5062
public PdfObject() {
5163

@@ -154,15 +166,15 @@ public <T extends PdfObject> T makeIndirect(PdfDocument document) {
154166
* @param state special flag to check
155167
* @return true if the state was set.
156168
*/
157-
protected boolean checkState(byte state) {
169+
protected boolean checkState(short state) {
158170
return (this.state & state) == state;
159171
}
160172

161173
/**
162174
* Sets special states of current object.
163175
* @param state special flag of current object
164176
*/
165-
protected <T extends PdfObject> T setState(byte state) {
177+
protected <T extends PdfObject> T setState(short state) {
166178
this.state |= state;
167179
return (T) this;
168180
}
@@ -171,8 +183,8 @@ protected <T extends PdfObject> T setState(byte state) {
171183
* Clear state of the flag of current object.
172184
* @param state special flag state to clear
173185
*/
174-
protected <T extends PdfObject> T clearState(byte state) {
175-
this.state &= 0xFF^state;
186+
protected <T extends PdfObject> T clearState(short state) {
187+
this.state &= ~state;
176188
return (T) this;
177189
}
178190

@@ -289,17 +301,26 @@ protected <T extends PdfObject> T setIndirectReference(PdfIndirectReference indi
289301

290302
//TODO comment! Add note about flush, modified flag and xref.
291303
public void setModified() {
292-
if (indirectReference != null)
304+
if (indirectReference != null) {
293305
indirectReference.setState(Modified);
306+
setState(ForbidRelease);
307+
}
294308
}
295309

296310
public void release() {
297-
if (indirectReference != null && indirectReference.getReader() != null
298-
&& !indirectReference.checkState(Flushed)) {
299-
indirectReference.refersTo = null;
300-
indirectReference = null;
311+
// In case ForbidRelease flag is set, release will not be performed.
312+
if (checkState(ForbidRelease)) {
313+
Logger logger = LoggerFactory.getLogger(PdfObject.class);
314+
logger.warn("ForbidRelease flag is set and release is called. Releasing will not be performed.");
315+
} else {
316+
if (indirectReference != null && indirectReference.getReader() != null
317+
&& !indirectReference.checkState(Flushed)) {
318+
indirectReference.refersTo = null;
319+
indirectReference = null;
320+
setState(ReadOnly);
321+
}
322+
//TODO log reasonless call of method
301323
}
302-
//TODO log reasonless call of method
303324
}
304325

305326
/**

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ protected void setPdfObject(T pdfObject){
144144
this.pdfObject = pdfObject;
145145
}
146146

147+
protected void setForbidRelease() {
148+
if (pdfObject != null) {
149+
pdfObject.setState(PdfObject.ForbidRelease);
150+
}
151+
}
152+
153+
protected void unsetForbidRelease() {
154+
if (pdfObject != null) {
155+
pdfObject.clearState(PdfObject.ForbidRelease);
156+
}
157+
}
158+
147159
protected static void markObjectAsIndirect(PdfObject pdfObject) {
148160
if (pdfObject.getIndirectReference() == null) {
149161
pdfObject.setState(PdfObject.MustBeIndirect);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ public PdfOutputStream write(PdfObject pdfObject) {
133133
pdfObject.makeIndirect(document);
134134
pdfObject = pdfObject.getIndirectReference();
135135
}
136+
if (pdfObject.checkState(PdfObject.ReadOnly)) {
137+
throw new PdfException(PdfException.CannotWriteObjectAfterItWasReleased);
138+
}
136139
switch (pdfObject.getType()) {
137140
case PdfObject.Array:
138141
write((PdfArray) pdfObject);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ public class PdfPage extends PdfObjectWrapper<PdfDictionary> {
4646

4747
protected PdfPage(PdfDictionary pdfObject) {
4848
super(pdfObject);
49+
setForbidRelease();
4950
ensureObjectIsAddedToDocument(pdfObject);
5051
}
5152

5253
protected PdfPage(PdfDocument pdfDocument, PageSize pageSize) {
53-
super(new PdfDictionary());
54-
makeIndirect(pdfDocument);
54+
this(new PdfDictionary().makeIndirect(pdfDocument));
5555
PdfStream contentStream = new PdfStream().makeIndirect(pdfDocument);
5656
getPdfObject().put(PdfName.Contents, contentStream);
5757
getPdfObject().put(PdfName.Type, PdfName.Page);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public PdfPages(int from, PdfDocument pdfDocument, PdfPages parent) {
1111
if (pdfDocument.getWriter() != null) {
1212
getPdfObject().makeIndirect(pdfDocument);
1313
}
14+
setForbidRelease();
1415
this.from = from;
1516
this.count = new PdfNumber(0);
1617
this.kids = new PdfArray();

0 commit comments

Comments
 (0)