@@ -23,6 +23,7 @@ This file is part of the iText (R) project.
23
23
package com .itextpdf .pdfa .checker ;
24
24
25
25
import com .itextpdf .commons .utils .MessageFormatUtil ;
26
+ import com .itextpdf .kernel .exceptions .PdfException ;
26
27
import com .itextpdf .kernel .pdf .PdfAConformanceLevel ;
27
28
import com .itextpdf .kernel .pdf .PdfArray ;
28
29
import com .itextpdf .kernel .pdf .PdfCatalog ;
@@ -34,15 +35,22 @@ This file is part of the iText (R) project.
34
35
import com .itextpdf .kernel .pdf .PdfString ;
35
36
import com .itextpdf .kernel .pdf .canvas .CanvasGraphicsState ;
36
37
import com .itextpdf .kernel .pdf .colorspace .PdfSpecialCs ;
38
+ import com .itextpdf .kernel .xmp .XMPConst ;
39
+ import com .itextpdf .kernel .xmp .XMPException ;
40
+ import com .itextpdf .kernel .xmp .XMPMeta ;
41
+ import com .itextpdf .kernel .xmp .XMPMetaFactory ;
42
+ import com .itextpdf .kernel .xmp .properties .XMPProperty ;
37
43
import com .itextpdf .pdfa .exceptions .PdfAConformanceException ;
38
44
import com .itextpdf .pdfa .exceptions .PdfaExceptionMessageConstant ;
39
45
import com .itextpdf .pdfa .logs .PdfAConformanceLogMessageConstant ;
40
46
47
+ import java .io .ByteArrayInputStream ;
41
48
import java .util .Arrays ;
42
49
import java .util .Collections ;
43
50
import java .util .HashSet ;
44
51
import java .util .Map ;
45
52
import java .util .Set ;
53
+ import java .util .regex .Pattern ;
46
54
import org .slf4j .Logger ;
47
55
import org .slf4j .LoggerFactory ;
48
56
@@ -129,6 +137,7 @@ public class PdfA4Checker extends PdfA3Checker {
129
137
PdfName .Bl
130
138
)));
131
139
140
+
132
141
/**
133
142
* Creates a PdfA4Checker with the required conformance level
134
143
*
@@ -148,7 +157,8 @@ protected void checkTrailer(PdfDictionary trailer) {
148
157
if (trailer .get (PdfName .Info ) != null ) {
149
158
PdfDictionary info = trailer .getAsDictionary (PdfName .Info );
150
159
if (info .size () != 1 || info .get (PdfName .ModDate ) == null ) {
151
- throw new PdfAConformanceException (PdfaExceptionMessageConstant .DOCUMENT_INFO_DICTIONARY_SHALL_ONLY_CONTAIN_MOD_DATE );
160
+ throw new PdfAConformanceException (
161
+ PdfaExceptionMessageConstant .DOCUMENT_INFO_DICTIONARY_SHALL_ONLY_CONTAIN_MOD_DATE );
152
162
}
153
163
}
154
164
}
@@ -160,18 +170,21 @@ protected void checkTrailer(PdfDictionary trailer) {
160
170
protected void checkCatalog (PdfCatalog catalog ) {
161
171
if ('2' != catalog .getDocument ().getPdfVersion ().toString ().charAt (4 )) {
162
172
throw new PdfAConformanceException (
163
- MessageFormatUtil .format (PdfaExceptionMessageConstant .THE_FILE_HEADER_SHALL_CONTAIN_RIGHT_PDF_VERSION , "2" ));
173
+ MessageFormatUtil .format (
174
+ PdfaExceptionMessageConstant .THE_FILE_HEADER_SHALL_CONTAIN_RIGHT_PDF_VERSION , "2" ));
164
175
}
165
176
PdfDictionary trailer = catalog .getDocument ().getTrailer ();
166
177
if (trailer .get (PdfName .Info ) != null ) {
167
178
if (catalog .getPdfObject ().get (PdfName .PieceInfo ) == null ) {
168
- throw new PdfAConformanceException (PdfaExceptionMessageConstant .DOCUMENT_SHALL_NOT_CONTAIN_INFO_UNLESS_THERE_IS_PIECE_INFO );
179
+ throw new PdfAConformanceException (
180
+ PdfaExceptionMessageConstant .DOCUMENT_SHALL_NOT_CONTAIN_INFO_UNLESS_THERE_IS_PIECE_INFO );
169
181
}
170
182
}
171
183
172
184
if ("F" .equals (conformanceLevel .getConformance ())) {
173
185
if (!catalog .nameTreeContainsKey (PdfName .EmbeddedFiles )) {
174
- throw new PdfAConformanceException (PdfaExceptionMessageConstant .NAME_DICTIONARY_SHALL_CONTAIN_EMBEDDED_FILES_KEY );
186
+ throw new PdfAConformanceException (
187
+ PdfaExceptionMessageConstant .NAME_DICTIONARY_SHALL_CONTAIN_EMBEDDED_FILES_KEY );
175
188
}
176
189
}
177
190
}
@@ -186,7 +199,8 @@ protected void checkCatalogValidEntries(PdfDictionary catalogDict) {
186
199
if (version != null && (version .toString ().charAt (0 ) != '2'
187
200
|| version .toString ().charAt (1 ) != '.' || !Character .isDigit (version .toString ().charAt (2 )))) {
188
201
throw new PdfAConformanceException (
189
- MessageFormatUtil .format (PdfaExceptionMessageConstant .THE_CATALOG_VERSION_SHALL_CONTAIN_RIGHT_PDF_VERSION , "2" ));
202
+ MessageFormatUtil .format (
203
+ PdfaExceptionMessageConstant .THE_CATALOG_VERSION_SHALL_CONTAIN_RIGHT_PDF_VERSION , "2" ));
190
204
}
191
205
}
192
206
@@ -196,10 +210,12 @@ protected void checkCatalogValidEntries(PdfDictionary catalogDict) {
196
210
@ Override
197
211
protected void checkFileSpec (PdfDictionary fileSpec ) {
198
212
if (fileSpec .getAsName (PdfName .AFRelationship ) == null ) {
199
- throw new PdfAConformanceException (PdfaExceptionMessageConstant .FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_AFRELATIONSHIP_KEY );
213
+ throw new PdfAConformanceException (
214
+ PdfaExceptionMessageConstant .FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_AFRELATIONSHIP_KEY );
200
215
}
201
216
if (!fileSpec .containsKey (PdfName .F ) || !fileSpec .containsKey (PdfName .UF )) {
202
- throw new PdfAConformanceException (PdfAConformanceException .FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_F_KEY_AND_UF_KEY );
217
+ throw new PdfAConformanceException (
218
+ PdfAConformanceException .FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_F_KEY_AND_UF_KEY );
203
219
}
204
220
if (!fileSpec .containsKey (PdfName .Desc )) {
205
221
LOGGER .warn (PdfAConformanceLogMessageConstant .FILE_SPECIFICATION_DICTIONARY_SHOULD_CONTAIN_DESC_KEY );
@@ -219,7 +235,8 @@ protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageR
219
235
}
220
236
if (pdfAOutputIntentColorSpace == null && pdfAPageOutputIntent == null
221
237
&& transparencyObjects .size () > 0
222
- && (pageDict .getAsDictionary (PdfName .Group ) == null || pageDict .getAsDictionary (PdfName .Group ).get (PdfName .CS ) == null )) {
238
+ && (pageDict .getAsDictionary (PdfName .Group ) == null
239
+ || pageDict .getAsDictionary (PdfName .Group ).get (PdfName .CS ) == null )) {
223
240
checkContentsForTransparency (pageDict );
224
241
checkAnnotationsForTransparency (pageDict .getAsArray (PdfName .Annots ));
225
242
checkResourcesForTransparency (pageResources , new HashSet <PdfObject >());
@@ -257,6 +274,7 @@ protected void checkPageAAConformance(PdfDictionary dict) {
257
274
}
258
275
259
276
//There are no limits for numbers in pdf-a/4
277
+
260
278
/**
261
279
* {@inheritDoc}
262
280
*/
@@ -266,6 +284,7 @@ protected void checkPdfNumber(PdfNumber number) {
266
284
}
267
285
268
286
//There is no limit for canvas stack in pdf-a/4
287
+
269
288
/**
270
289
* {@inheritDoc}
271
290
*/
@@ -284,6 +303,7 @@ protected int getMaxStringLength() {
284
303
}
285
304
286
305
//There is no limit for DeviceN components count in pdf-a/4
306
+
287
307
/**
288
308
* {@inheritDoc}
289
309
*/
@@ -340,7 +360,8 @@ protected void checkAnnotation(PdfDictionary annotDic) {
340
360
// Extra check for blending mode
341
361
PdfName blendMode = annotDic .getAsName (PdfName .BM );
342
362
if (blendMode != null && !allowedBlendModes4 .contains (blendMode )) {
343
- throw new PdfAConformanceException (PdfaExceptionMessageConstant .ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_A_GRAPHIC_STATE_AND_ANNOTATION_DICTIONARY );
363
+ throw new PdfAConformanceException (
364
+ PdfaExceptionMessageConstant .ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_A_GRAPHIC_STATE_AND_ANNOTATION_DICTIONARY );
344
365
}
345
366
346
367
// And then treat the annotation as an object with transparency
@@ -386,6 +407,24 @@ protected void checkWidgetAAConformance(PdfDictionary dict) {
386
407
}
387
408
}
388
409
410
+ /**
411
+ * @param catalog the catalog {@link PdfDictionary} to check
412
+ */
413
+ @ Override
414
+ protected void checkMetaData (PdfDictionary catalog ) {
415
+ super .checkMetaData (catalog );
416
+ try {
417
+ final PdfStream xmpMetadata = catalog .getAsStream (PdfName .Metadata );
418
+ byte [] bytes = xmpMetadata .getBytes ();
419
+ checkPacketHeader (bytes );
420
+ final XMPMeta meta = XMPMetaFactory .parse (new ByteArrayInputStream (bytes ));
421
+ checkVersionIdentification (meta );
422
+ checkFileProvenanceSpec (meta );
423
+ } catch (XMPException ex ) {
424
+ throw new PdfException (ex );
425
+ }
426
+ }
427
+
389
428
/**
390
429
* {@inheritDoc}
391
430
*/
@@ -440,7 +479,119 @@ protected String getTransparencyErrorMessage() {
440
479
@ Override
441
480
protected void checkBlendMode (PdfName blendMode ) {
442
481
if (!allowedBlendModes4 .contains (blendMode )) {
443
- throw new PdfAConformanceException (PdfAConformanceException .ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY );
482
+ throw new PdfAConformanceException (
483
+ PdfAConformanceException .ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY );
484
+ }
485
+ }
486
+
487
+
488
+ private static boolean isValidXmpConformance (String value ) {
489
+ if (value == null ) {
490
+ return false ;
491
+ }
492
+ if (value .length () != 1 ) {
493
+ return false ;
494
+ }
495
+ return "F" .equals (value ) || "E" .equals (value );
496
+ }
497
+
498
+ private static boolean isValidXmpRevision (String value ) {
499
+ if (value == null ) {
500
+ return false ;
501
+ }
502
+ if (value .length () != 4 ) {
503
+ return false ;
504
+ }
505
+ for (final char c : value .toCharArray ()) {
506
+ if (!Character .isDigit (c )) {
507
+ return false ;
508
+ }
509
+ }
510
+ return true ;
511
+ }
512
+
513
+
514
+ private void checkPacketHeader (byte [] meta ) {
515
+ if (meta == null ) {
516
+ return ;
517
+ }
518
+ final String metAsStr = new String (meta );
519
+ final String regex = "<\\ ?xpacket.*encoding|bytes.*\\ ?>" ;
520
+ final Pattern pattern = Pattern .compile (regex );
521
+ if (pattern .matcher (metAsStr ).find ()) {
522
+ throw new PdfAConformanceException (
523
+ PdfaExceptionMessageConstant
524
+ .XMP_METADATA_HEADER_PACKET_MAY_NOT_CONTAIN_BYTES_OR_ENCODING_ATTRIBUTE );
525
+ }
526
+ }
527
+
528
+
529
+ private void checkFileProvenanceSpec (XMPMeta meta ) {
530
+ try {
531
+ XMPProperty history = meta .getProperty (XMPConst .NS_XMP_MM , XMPConst .HISTORY );
532
+ if (history == null ) {
533
+ return ;
534
+ }
535
+ if (!history .getOptions ().isArray ()) {
536
+ return ;
537
+ }
538
+ final int amountOfEntries = meta .countArrayItems (XMPConst .NS_XMP_MM , XMPConst .HISTORY );
539
+ for (int i = 0 ; i < amountOfEntries ; i ++) {
540
+ int nameSpaceIndex = i + 1 ;
541
+ if (!meta .doesPropertyExist (XMPConst .NS_XMP_MM ,
542
+ XMPConst .HISTORY + "[" + nameSpaceIndex + "]/stEvt:action" )) {
543
+ throw new PdfAConformanceException (MessageFormatUtil .format (
544
+ PdfaExceptionMessageConstant .XMP_METADATA_HISTORY_ENTRY_SHALL_CONTAIN_KEY ,
545
+ "stEvt:action" ));
546
+ }
547
+ if (!meta .doesPropertyExist (XMPConst .NS_XMP_MM ,
548
+ XMPConst .HISTORY + "[" + nameSpaceIndex + "]/stEvt:when" )) {
549
+ throw new PdfAConformanceException (MessageFormatUtil .format (
550
+ PdfaExceptionMessageConstant .XMP_METADATA_HISTORY_ENTRY_SHALL_CONTAIN_KEY ,
551
+ "stEvt:when" ));
552
+ }
553
+ }
554
+
555
+
556
+ } catch (XMPException e ) {
557
+ throw new PdfException (e );
558
+ }
559
+ }
560
+
561
+
562
+ private void checkVersionIdentification (XMPMeta meta ) {
563
+ try {
564
+ XMPProperty prop = meta .getProperty (XMPConst .NS_PDFA_ID , XMPConst .PART );
565
+ if (prop == null || !getConformanceLevel ().getPart ().equals (prop .getValue ())) {
566
+ throw new PdfAConformanceException (MessageFormatUtil .format (
567
+ PdfaExceptionMessageConstant .XMP_METADATA_HEADER_SHALL_CONTAIN_VERSION_IDENTIFIER_PART ,
568
+ getConformanceLevel ().getPart ()));
569
+ }
570
+ } catch (XMPException e ) {
571
+ throw new PdfAConformanceException (MessageFormatUtil .format (
572
+ PdfaExceptionMessageConstant .XMP_METADATA_HEADER_SHALL_CONTAIN_VERSION_IDENTIFIER_PART ,
573
+ getConformanceLevel ().getPart ()));
574
+ }
575
+
576
+ try {
577
+ XMPProperty prop = meta .getProperty (XMPConst .NS_PDFA_ID , XMPConst .REV );
578
+ if (prop == null || !isValidXmpRevision (prop .getValue ())) {
579
+ throw new PdfAConformanceException (
580
+ PdfaExceptionMessageConstant .XMP_METADATA_HEADER_SHALL_CONTAIN_VERSION_IDENTIFIER_REV );
581
+ }
582
+ } catch (XMPException e ) {
583
+ throw new PdfAConformanceException (
584
+ PdfaExceptionMessageConstant .XMP_METADATA_HEADER_SHALL_CONTAIN_VERSION_IDENTIFIER_REV );
585
+ }
586
+
587
+ try {
588
+ XMPProperty prop = meta .getProperty (XMPConst .NS_PDFA_ID , XMPConst .CONFORMANCE );
589
+ if (prop != null && !isValidXmpConformance (prop .getValue ())) {
590
+ throw new PdfAConformanceException (
591
+ PdfaExceptionMessageConstant .XMP_METADATA_HEADER_SHALL_CONTAIN_VERSION_IDENTIFIER_CONFORMANCE );
592
+ }
593
+ } catch (XMPException e ) {
594
+ // ignored because it is not required
444
595
}
445
596
}
446
597
0 commit comments