1# Before "make install", this script should be runnable with "make test".
2# After "make install" it should work as "perl t/PDF.t".
3
4BEGIN {
5    $| = 1; print "1..26\n"; $Image::ExifTool::configFile = '';
6    require './t/TestLib.pm'; t::TestLib->import();
7}
8END {print "not ok 1\n" unless $loaded;}
9
10# test 1: Load the module(s)
11use Image::ExifTool 'ImageInfo';
12use Image::ExifTool::PDF;
13$loaded = 1;
14print "ok 1\n";
15
16my $testname = 'PDF';
17my $testnum = 1;
18
19#------------------------------------------------------------------------------
20# PDF decryption test
21# Inputs: 0) Encrypt object reference, plus additional entries (see below),
22#         1) Test number, 2) encrypt flag (false for decryption)
23# Returns: nothing, but prints test result
24# Additional encrypt hash entries used by this routine:
25#   _id  - PDF file ID
26#   _ref - PDF object reference string
27#   _req - other module required for this test
28#   _ciphertext - encrypted data
29#   _plaintext  - expected decryption result
30#   _password   - password for decryption (if used)
31sub CryptTest($$;$)
32{
33    my ($cryptInfo, $testNum, $encrypt) = @_;
34    my $skip = '';
35    if (eval "require $$cryptInfo{_req}") {
36        my $exifTool = new Image::ExifTool;
37        $exifTool->Options('Password', $$cryptInfo{_password});
38        my $err = Image::ExifTool::PDF::DecryptInit($exifTool, $cryptInfo, $$cryptInfo{_id});
39        unless ($err) {
40            my $data = $$cryptInfo{$encrypt ? '_plaintext' : '_ciphertext'};
41            Image::ExifTool::PDF::Crypt(\$data, $$cryptInfo{_ref} || '1 0 R', $encrypt);
42            $err = $$cryptInfo{_error};
43            if (not $err and $data ne $$cryptInfo{$encrypt ? '_ciphertext' : '_plaintext'}) {
44                $err = "Test $testnum (decryption) returned wrong value:\n    " . unpack('H*',$data);
45            }
46        }
47        if ($err) {
48            warn "\n  $err\n";
49            print 'not ';
50        }
51    } else {
52        $skip = " # skip Requires $$cryptInfo{_req}";
53    }
54    print "ok $testnum$skip\n";
55}
56
57# test 2: Extract information from PDF.pdf
58{
59    ++$testnum;
60    my $exifTool = new Image::ExifTool;
61    my $info = $exifTool->ImageInfo('t/images/PDF.pdf');
62    print 'not ' unless check($exifTool, $info, $testname, $testnum);
63    print "ok $testnum\n";
64}
65
66# test 3: Test Standard PDF decryption
67{
68    ++$testnum;
69    my %cryptInfo = (
70        Filter => '/Standard',
71        P => -60,
72        V => 1,
73        R => 0,
74        O => '<2055c756c72e1ad702608e8196acad447ad32d17cff583235f6dd15fed7dab67>',
75        U => '<7150bd1da9d292af3627fca6a8dde1d696e25312041aed09059f9daee04353ae>',
76        _id => pack('H*','12116a1a124ae4cd8179e8978f6ac88b'),
77        _req => 'Digest::MD5',
78        _ref => '4 0 R',
79        _ciphertext => pack('N', 0x34a290d3),
80        _plaintext => pack('N', 0x5924d335),
81    );
82    CryptTest(\%cryptInfo, $testNum);
83}
84
85# tests 4-21: Test writing, deleting and reverting two different files
86{
87    # do a bunch of edits
88    my @edits = ([  # (on file containing both PDF Info and XMP)
89        [   # test 4: write PDF and XMP information
90            [ 'PDF:Creator' => 'me'],
91            [ 'XMP:Creator' => 'you' ],
92            [ 'AllDates'    => '2:30', Shift => -1 ],
93        ],[ # test 5: delete all PDF
94            [ 'PDF:all' ],
95        ],[ # test 6: write some XMP
96            [ 'XMP:Author' => 'them' ],
97        ],[ # test 7: create new PDF
98            [ 'PDF:Keywords'  => 'one' ],
99            [ 'PDF:Keywords'  => 'two' ],
100            [ 'AppleKeywords' => 'three' ],
101            [ 'AppleKeywords' => 'four' ],
102        ],[ # test 8: delete all XMP
103            [ 'XMP:all' ],
104        ],[ # test 9: write some PDF
105            [ 'PDF:Keywords'  => 'another one', AddValue => 1 ],
106            [ 'AppleKeywords' => 'three',       DelValue => 1 ],
107        ],[ # test 10: create new XMP
108            [ 'XMP:Author' => 'us' ],
109        ],[ # test 11: write some PDF
110            [ 'PDF:Keywords'  => 'two',  DelValue => 1 ],
111            [ 'AppleKeywords' => 'five', AddValue => 1 ],
112        ],[ # test 12: delete re-added XMP
113            [ 'XMP:all' ],
114        ],
115    ],[             # (on file without PDF Info or XMP)
116        [   # test 14: create new XMP
117            [ 'XMP:Author' => 'him' ],
118        ],[ # test 15: create new PDF
119            [ 'PDF:Author' => 'her' ],
120        ],[ # test 16: delete XMP and PDF
121            [ 'XMP:all' ],
122            [ 'PDF:all' ],
123        ],[ # test 17: delete XMP and PDF again
124            [ 'XMP:all' ],
125            [ 'PDF:all' ],
126        ],[ # test 18: create new PDF
127            [ 'PDF:Author' => 'it' ],
128        ],[ # test 19: create new XMP
129            [ 'XMP:Author' => 'thing' ],
130        ],[ # test 20: delete all
131            [ 'all' ],
132        ],
133    ]);
134    my $testSet;
135    foreach $testSet (0,1) {
136        my ($edit, $testfile2, $lastOK);
137        my $testfile = 't/images/' . ($testSet ? 'PDF2.pdf' : 'PDF.pdf');
138        my $testfile1 = $testfile;
139        my $exifTool = new Image::ExifTool;
140        $exifTool->Options(PrintConv => 0);
141        foreach $edit (@{$edits[$testSet]}) {
142            ++$testnum;
143            $exifTool->SetNewValue();
144            $exifTool->SetNewValue(@$_) foreach @$edit;
145            $testfile2 = "t/${testname}_${testnum}_failed.pdf";
146            unlink $testfile2;
147            $exifTool->WriteInfo($testfile1, $testfile2);
148            my $info = $exifTool->ImageInfo($testfile2,
149                    qw{Filesize PDF:all XMP:Creator XMP:Author AllDates});
150            my $ok = check($exifTool, $info, $testname, $testnum);
151            print 'not ' unless $ok;
152            print "ok $testnum\n";
153            # erase source file if previous test was OK
154            unlink $testfile1 if $lastOK;
155            $lastOK = $ok;
156            $testfile1 = $testfile2;    # use this file for the next test
157        }
158        # revert all edits and compare with original file
159        ++$testnum;
160        $exifTool->SetNewValue('PDF-update:all');
161        $testfile2 = "t/${testname}_${testnum}_failed.pdf";
162        unlink $testfile2;
163        $exifTool->WriteInfo($testfile1, $testfile2);
164        if (binaryCompare($testfile2, $testfile)) {
165            unlink $testfile2;
166        } else {
167            print 'not ';
168        }
169        print "ok $testnum\n";
170        unlink $testfile1 if $lastOK;
171    }
172}
173
174# test 22: Delete all tags
175{
176    ++$testnum;
177    my $exifTool = new Image::ExifTool;
178    my $testfile = "t/${testname}_${testnum}_failed.pdf";
179    unlink $testfile;
180    $exifTool->Options(IgnoreMinorErrors => 1);
181    $exifTool->SetNewValue(all => undef);
182    my $ok = writeInfo($exifTool, 't/images/PDF.pdf', $testfile);
183    $exifTool->Options(IgnoreMinorErrors => 0);
184    my $info = $exifTool->ImageInfo($testfile,'pdf:all','xmp:all',{Duplicates=>1,Unknown=>1});
185    if ($ok and check($exifTool, $info, $testname, $testnum)) {
186        unlink $testfile;
187    } else {
188        print 'not ';
189    }
190    print "ok $testnum\n";
191}
192
193# test 23: Test AES decryption alone (tests 24-26 require Digest::MD5 or Digest::SHA)
194{
195    ++$testnum;
196    require Image::ExifTool::AES;
197    my $data = pack('H*','6fdc3ca684348bc8f31379aa46455d7b60c0989e027c1d82e746f136d6e95b7485735793ff64310e5b9e367dcc26f564');
198    my $err = Image::ExifTool::AES::Crypt(\$data, '11223344556677889900112233445566');
199    if ($err) {
200        warn "\n  $err\n";
201        print 'not ';
202    } elsif ($data ne 'ExifTool AES Test') {
203        my $hex = unpack 'H*', $data;
204        warn "\n  Incorrect result from AES decryption:\n";
205        warn "    $hex\n";
206        print 'not ';
207    }
208    print "ok $testnum\n";
209}
210
211# test 24-26: Test AESV2 and AESV3 decryption
212{
213    my @encrypt = (
214    # AESV2 without password
215    {
216        Filter => '/Standard',
217        V => 4,
218        R => 4,
219        P => -1340,
220        Length => 128,
221        StmF => '/StdCF',
222        StrF => '/StdCF',
223        CF => {
224            StdCF => {
225                AuthEvent => '/DocOpen',
226                CFM => '/AESV2',
227                Length => 16,
228            },
229        },
230        EncryptMetadata => 'false',
231        O => '<181ee8e93a99fa1c2a534dd68a5ab07c54268cfe8fbf28c468316b6f732674c1>',
232        U => '<a3525aef4143f4419c78b109317f0e5200000000000000000000000000000000>',
233        _req => 'Digest::MD5',
234        _ref => '4 0 R',
235        _id => pack('H*','d0a736f05faf64c6b52dea82a2ad53e0'),
236        _plaintext => 'This was a test',
237        _ciphertext => pack('H*', 'a86b5e00d9c7e4455cf5d8cedf195c2060e1467ea6d698876a77e9a66cb7867c'),
238    },
239    # AESV3 without password
240    {
241        Filter => '/Standard',
242        V => 5,
243        R => 5,
244        P => -1028,
245        Length => 256,
246        StmF => '/StdCF',
247        StrF => '/StdCF',
248        CF => {
249            StdCF => {
250                AuthEvent => '/DocOpen',
251                CFM => '/AESV3',
252                Length => 32,
253            },
254        },
255        Perms => '<014ee28fe2b91e2198a593b7c3b22f50>',
256        O => '<83e5edfcdecbe2ebe6d519dbafe80fd453028dda119eb76d0216e1344392320d60e1467ea6d698876a77e9a66cb7867c>',
257        U => '<e5e7ade8aebdc9413a0fd176efc4081bdbad3b16a67ece7a01fadb24010a003ea86b5e00d9c7e4455cf5d8cedf195c20>',
258        OE => '<a29f37f1f085b575d9016daad05ca466dd073ba5d067cc7ffa8ef7d1605e460e>',
259        UE => '<47ea891b25af77aaceccf8f2fdeff0c09e9d0f67275f059dbfabbb18fcbf848d>',
260        _req => 'Digest::SHA',
261        _ref => 'dummy',
262        _id => pack('H*','618cb5be1d82fceea9a501b62d408296'),
263        _plaintext => 'This was a test',
264        _ciphertext => pack('H*', 'e90756e8fd60fb7390c34d931e3e3d61898cd133e613e8cf86cd40f7b207a62d'),
265    },
266    # AESV3 with a password
267    {
268        Filter => '/Standard',
269        V => 5,
270        R => 5,
271        P => -1028,
272        Length => 256,
273        StmF => '/StdCF',
274        StrF => '/StdCF',
275        CF => {
276            StdCF => {
277                AuthEvent => '/DocOpen',
278                CFM => '/AESV3',
279                Length => 32,
280            },
281        },
282        Perms => '<014ee28fe2b91e2198a593b7c3b22f50>',
283        O => '<31eefe924a298d8bf19eafc9be6abdfa65a97478f94e907dccff5ac000b83fa521167b86cf70bf77d4a054bc9a59573d>',
284        U => '<6525a788c2ebf27baa45f526bcdb9d2f96c3c67ae1c62324135cac0b823451ba9ad8edb68d167d2d8370d799c41d17d7>',
285        OE => '<ea58e3c731999cdc0f8a395c7391836c2b2db0b4ac86439b3fe5692ddc71671a>',
286        UE => '<f6818f43e176dfe8912f62717032169cf48854f540f7b7641be363ef50371f07>',
287        _req => 'Digest::SHA',
288        _ref => 'dummy',
289        _id => pack('H*','b5f9d17b07152a45bc0a939727c389ad'),
290        _plaintext => 'This was a test',
291        _password => 'ExifTool',
292        _ciphertext => pack('H*', '8bb3565d8c4b9df8cc350954d9f91a46aa47e40eeb5a0cff559acd5ec3e94244'),
293    });
294    my $exifTool = new Image::ExifTool;
295    my $cryptInfo;
296    foreach $cryptInfo (@encrypt) {
297        ++$testnum;
298        my $encrypt = 0;    # (set to 1 to generate ciphertext strings)
299        CryptTest($cryptInfo, $testNum, $encrypt);
300    }
301}
302
303
304# end
305