@@ -55,6 +55,7 @@ This file is part of the iText (R) project.
55
55
import com .itextpdf .kernel .exceptions .PdfException ;
56
56
import com .itextpdf .kernel .crypto .securityhandler .UnsupportedSecurityHandlerException ;
57
57
import com .itextpdf .kernel .exceptions .KernelExceptionMessageConstant ;
58
+ import com .itextpdf .kernel .exceptions .XrefCycledReferencesException ;
58
59
import com .itextpdf .kernel .pdf .filters .FilterHandlers ;
59
60
import com .itextpdf .kernel .pdf .filters .IFilterHandler ;
60
61
@@ -63,10 +64,13 @@ This file is part of the iText (R) project.
63
64
import java .io .FileNotFoundException ;
64
65
import java .io .IOException ;
65
66
import java .io .InputStream ;
67
+ import java .util .HashSet ;
66
68
import java .util .Map ;
67
69
68
70
import com .itextpdf .kernel .xmp .XMPException ;
69
71
import com .itextpdf .kernel .xmp .XMPMetaFactory ;
72
+
73
+ import java .util .Set ;
70
74
import org .slf4j .Logger ;
71
75
import org .slf4j .LoggerFactory ;
72
76
@@ -724,6 +728,10 @@ protected void readPdf() throws IOException {
724
728
}
725
729
try {
726
730
readXref ();
731
+ } catch (XrefCycledReferencesException ex ) {
732
+ // Throws an exception when xref stream has cycled references(due to lack of opportunity to fix such an
733
+ // issue) or xref tables have cycled references and PdfReader.StrictnessLevel set to CONSERVATIVE.
734
+ throw ex ;
727
735
} catch (RuntimeException ex ) {
728
736
Logger logger = LoggerFactory .getLogger (PdfReader .class );
729
737
logger .error (IoLogMessageConstant .XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT , ex );
@@ -961,11 +969,13 @@ protected PdfArray readArray(boolean objStm) throws IOException {
961
969
protected void readXref () throws IOException {
962
970
tokens .seek (tokens .getStartxref ());
963
971
tokens .nextToken ();
964
- if (!tokens .tokenValueEqualsTo (PdfTokenizer .Startxref ))
972
+ if (!tokens .tokenValueEqualsTo (PdfTokenizer .Startxref )) {
965
973
throw new PdfException (KernelExceptionMessageConstant .PDF_STARTXREF_NOT_FOUND , tokens );
974
+ }
966
975
tokens .nextToken ();
967
- if (tokens .getTokenType () != PdfTokenizer .TokenType .Number )
976
+ if (tokens .getTokenType () != PdfTokenizer .TokenType .Number ) {
968
977
throw new PdfException (KernelExceptionMessageConstant .PDF_STARTXREF_IS_NOT_FOLLOWED_BY_A_NUMBER , tokens );
978
+ }
969
979
long startxref = tokens .getLongValue ();
970
980
lastXref = startxref ;
971
981
eofPos = tokens .getPosition ();
@@ -974,27 +984,41 @@ protected void readXref() throws IOException {
974
984
xrefStm = true ;
975
985
return ;
976
986
}
987
+ } catch (XrefCycledReferencesException cycledReferencesException ) {
988
+ throw cycledReferencesException ;
977
989
} catch (Exception ignored ) {
990
+ // Do nothing.
978
991
}
979
992
// clear xref because of possible issues at reading xref stream.
980
993
pdfDocument .getXref ().clear ();
981
994
982
995
tokens .seek (startxref );
983
996
trailer = readXrefSection ();
984
997
985
- // Prev key - integer value
986
- // (Present only if the file has more than one cross-reference section; shall be an indirect reference)
998
+ // Prev key - integer value.
999
+ // (Present only if the file has more than one cross-reference section; shall be an indirect reference).
987
1000
// The byte offset in the decoded stream from the beginning of the file
988
1001
// to the beginning of the previous cross-reference section.
989
1002
PdfDictionary trailer2 = trailer ;
1003
+ final Set <Long > alreadyVisitedXrefTables = new HashSet <>();
990
1004
while (true ) {
1005
+ alreadyVisitedXrefTables .add (startxref );
991
1006
PdfNumber prev = (PdfNumber ) trailer2 .get (PdfName .Prev );
992
- if (prev == null )
1007
+ if (prev == null ) {
993
1008
break ;
994
- if (prev .longValue () == startxref )
995
- throw new PdfException (KernelExceptionMessageConstant .
996
- TRAILER_PREV_ENTRY_POINTS_TO_ITS_OWN_CROSS_REFERENCE_SECTION );
997
- startxref = prev .longValue ();
1009
+ }
1010
+ long prevXrefOffset = prev .longValue ();
1011
+ if (alreadyVisitedXrefTables .contains (prevXrefOffset )) {
1012
+ if (StrictnessLevel .CONSERVATIVE .isStricter (this .getStrictnessLevel ())) {
1013
+ // Throw the exception to rebuild xref table, it'll be caught in method above.
1014
+ throw new PdfException (KernelExceptionMessageConstant .
1015
+ TRAILER_PREV_ENTRY_POINTS_TO_ITS_OWN_CROSS_REFERENCE_SECTION );
1016
+ } else {
1017
+ throw new XrefCycledReferencesException (
1018
+ KernelExceptionMessageConstant .XREF_TABLE_HAS_CYCLED_REFERENCES );
1019
+ }
1020
+ }
1021
+ startxref = prevXrefOffset ;
998
1022
tokens .seek (startxref );
999
1023
trailer2 = readXrefSection ();
1000
1024
}
@@ -1098,6 +1122,7 @@ protected PdfDictionary readXrefSection() throws IOException {
1098
1122
}
1099
1123
1100
1124
protected boolean readXrefStream (long ptr ) throws IOException {
1125
+ final Set <Long > alreadyVisitedXrefStreams = new HashSet <>();
1101
1126
while (ptr != -1 ) {
1102
1127
tokens .seek (ptr );
1103
1128
if (!tokens .nextToken ()) {
@@ -1112,6 +1137,7 @@ protected boolean readXrefStream(long ptr) throws IOException {
1112
1137
if (!tokens .nextToken () || !tokens .tokenValueEqualsTo (PdfTokenizer .Obj )) {
1113
1138
return false ;
1114
1139
}
1140
+ alreadyVisitedXrefStreams .add (ptr );
1115
1141
PdfXrefTable xref = pdfDocument .getXref ();
1116
1142
PdfObject object = readObject (false );
1117
1143
PdfStream xrefStream ;
@@ -1208,6 +1234,10 @@ protected boolean readXrefStream(long ptr) throws IOException {
1208
1234
}
1209
1235
}
1210
1236
ptr = prev ;
1237
+ if (alreadyVisitedXrefStreams .contains (ptr )) {
1238
+ throw new XrefCycledReferencesException (
1239
+ KernelExceptionMessageConstant .XREF_STREAM_HAS_CYCLED_REFERENCES );
1240
+ }
1211
1241
}
1212
1242
return true ;
1213
1243
}
0 commit comments