1 /*
2 * $Id: PdfReader.java 3537 2008-07-08 08:53:00Z blowagie $
3 *
4 * Copyright 2001, 2002 Paulo Soares
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text.pdf;
51
52 import java.io.ByteArrayInputStream;
53 import java.io.ByteArrayOutputStream;
54 import java.io.DataInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.net.URL;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Set;
66 import java.util.zip.InflaterInputStream;
67 import java.util.Stack;
68 import java.security.Key;
69 import java.security.MessageDigest;
70 import java.security.cert.Certificate;
71
72 import com.lowagie.text.ExceptionConverter;
73 import com.lowagie.text.PageSize;
74 import com.lowagie.text.Rectangle;
75 import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
76 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
77
78 import org.bouncycastle.cms.CMSEnvelopedData;
79 import org.bouncycastle.cms.RecipientInformation;
80
81 /** Reads a PDF document.
82 * @author Paulo Soares (psoares@consiste.pt)
83 * @author Kazuya Ujihara
84 */
85 public class PdfReader implements PdfViewerPreferences {
86
87 static final PdfName pageInhCandidates[] = {
88 PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX
89 };
90
91 static final byte endstream[] = PdfEncodings.convertToBytes("endstream", null);
92 static final byte endobj[] = PdfEncodings.convertToBytes("endobj", null);
93 protected PRTokeniser tokens;
94 // Each xref pair is a position
95 // type 0 -> -1, 0
96 // type 1 -> offset, 0
97 // type 2 -> index, obj num
98 protected int xref[];
99 protected HashMap objStmMark;
100 protected IntHashtable objStmToOffset;
101 protected boolean newXrefType;
102 private ArrayList xrefObj;
103 PdfDictionary rootPages;
104 protected PdfDictionary trailer;
105 protected PdfDictionary catalog;
106 protected PageRefs pageRefs;
107 protected PRAcroForm acroForm = null;
108 protected boolean acroFormParsed = false;
109 protected boolean encrypted = false;
110 protected boolean rebuilt = false;
111 protected int freeXref;
112 protected boolean tampered = false;
113 protected int lastXref;
114 protected int eofPos;
115 protected char pdfVersion;
116 protected PdfEncryption decrypt;
117 protected byte password[] = null; //added by ujihara for decryption
118 protected Key certificateKey = null; //added by Aiken Sam for certificate decryption
119 protected Certificate certificate = null; //added by Aiken Sam for certificate decryption
120 protected String certificateKeyProvider = null; //added by Aiken Sam for certificate decryption
121 private boolean ownerPasswordUsed;
122 protected ArrayList strings = new ArrayList();
123 protected boolean sharedStreams = true;
124 protected boolean consolidateNamedDestinations = false;
125 protected int rValue;
126 protected int pValue;
127 private int objNum;
128 private int objGen;
129 private int fileLength;
130 private boolean hybridXref;
131 private int lastXrefPartial = -1;
132 private boolean partial;
133 private PRIndirectReference cryptoRef;
134 private PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
135 private boolean encryptionError;
136
137 /**
138 * Holds value of property appendable.
139 */
140 private boolean appendable;
141
142 protected PdfReader() {
143 }
144
145 /** Reads and parses a PDF document.
146 * @param filename the file name of the document
147 * @throws IOException on error
148 */
149 public PdfReader(String filename) throws IOException {
150 this(filename, null);
151 }
152
153 /** Reads and parses a PDF document.
154 * @param filename the file name of the document
155 * @param ownerPassword the password to read the document
156 * @throws IOException on error
157 */
158 public PdfReader(String filename, byte ownerPassword[]) throws IOException {
159 password = ownerPassword;
160 tokens = new PRTokeniser(filename);
161 readPdf();
162 }
163
164 /** Reads and parses a PDF document.
165 * @param pdfIn the byte array with the document
166 * @throws IOException on error
167 */
168 public PdfReader(byte pdfIn[]) throws IOException {
169 this(pdfIn, null);
170 }
171
172 /** Reads and parses a PDF document.
173 * @param pdfIn the byte array with the document
174 * @param ownerPassword the password to read the document
175 * @throws IOException on error
176 */
177 public PdfReader(byte pdfIn[], byte ownerPassword[]) throws IOException {
178 password = ownerPassword;
179 tokens = new PRTokeniser(pdfIn);
180 readPdf();
181 }
182
183 /** Reads and parses a PDF document.
184 * @param filename the file name of the document
185 * @param certificate the certificate to read the document
186 * @param certificateKey the private key of the certificate
187 * @param certificateKeyProvider the security provider for certificateKey
188 * @throws IOException on error
189 */
190 public PdfReader(String filename, Certificate certificate, Key certificateKey, String certificateKeyProvider) throws IOException {
191 this.certificate = certificate;
192 this.certificateKey = certificateKey;
193 this.certificateKeyProvider = certificateKeyProvider;
194 tokens = new PRTokeniser(filename);
195 readPdf();
196 }
197
198 /** Reads and parses a PDF document.
199 * @param url the URL of the document
200 * @throws IOException on error
201 */
202 public PdfReader(URL url) throws IOException {
203 this(url, null);
204 }
205
206 /** Reads and parses a PDF document.
207 * @param url the URL of the document
208 * @param ownerPassword the password to read the document
209 * @throws IOException on error
210 */
211 public PdfReader(URL url, byte ownerPassword[]) throws IOException {
212 password = ownerPassword;
213 tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
214 readPdf();
215 }
216
217 /**
218 * Reads and parses a PDF document.
219 * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
220 * end but is not closed
221 * @param ownerPassword the password to read the document
222 * @throws IOException on error
223 */
224 public PdfReader(InputStream is, byte ownerPassword[]) throws IOException {
225 password = ownerPassword;
226 tokens = new PRTokeniser(new RandomAccessFileOrArray(is));
227 readPdf();
228 }
229
230 /**
231 * Reads and parses a PDF document.
232 * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
233 * end but is not closed
234 * @throws IOException on error
235 */
236 public PdfReader(InputStream is) throws IOException {
237 this(is, null);
238 }
239
240 /**
241 * Reads and parses a pdf document. Contrary to the other constructors only the xref is read
242 * into memory. The reader is said to be working in "partial" mode as only parts of the pdf
243 * are read as needed. The pdf is left open but may be closed at any time with
244 * <CODE>PdfReader.close()</CODE>, reopen is automatic.
245 * @param raf the document location
246 * @param ownerPassword the password or <CODE>null</CODE> for no password
247 * @throws IOException on error
248 */
249 public PdfReader(RandomAccessFileOrArray raf, byte ownerPassword[]) throws IOException {
250 password = ownerPassword;
251 partial = true;
252 tokens = new PRTokeniser(raf);
253 readPdfPartial();
254 }
255
256 /** Creates an independent duplicate.
257 * @param reader the <CODE>PdfReader</CODE> to duplicate
258 */
259 public PdfReader(PdfReader reader) {
260 this.appendable = reader.appendable;
261 this.consolidateNamedDestinations = reader.consolidateNamedDestinations;
262 this.encrypted = reader.encrypted;
263 this.rebuilt = reader.rebuilt;
264 this.sharedStreams = reader.sharedStreams;
265 this.tampered = reader.tampered;
266 this.password = reader.password;
267 this.pdfVersion = reader.pdfVersion;
268 this.eofPos = reader.eofPos;
269 this.freeXref = reader.freeXref;
270 this.lastXref = reader.lastXref;
271 this.tokens = new PRTokeniser(reader.tokens.getSafeFile());
272 if (reader.decrypt != null)
273 this.decrypt = new PdfEncryption(reader.decrypt);
274 this.pValue = reader.pValue;
275 this.rValue = reader.rValue;
276 this.xrefObj = new ArrayList(reader.xrefObj);
277 for (int k = 0; k < reader.xrefObj.size(); ++k) {
278 this.xrefObj.set(k, duplicatePdfObject((PdfObject)reader.xrefObj.get(k), this));
279 }
280 this.pageRefs = new PageRefs(reader.pageRefs, this);
281 this.trailer = (PdfDictionary)duplicatePdfObject(reader.trailer, this);
282 this.catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
283 this.rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
284 this.fileLength = reader.fileLength;
285 this.partial = reader.partial;
286 this.hybridXref = reader.hybridXref;
287 this.objStmToOffset = reader.objStmToOffset;
288 this.xref = reader.xref;
289 this.cryptoRef = (PRIndirectReference)duplicatePdfObject(reader.cryptoRef, this);
290 this.ownerPasswordUsed = reader.ownerPasswordUsed;
291 }
292
293 /** Gets a new file instance of the original PDF
294 * document.
295 * @return a new file instance of the original PDF document
296 */
297 public RandomAccessFileOrArray getSafeFile() {
298 return tokens.getSafeFile();
299 }
300
301 protected PdfReaderInstance getPdfReaderInstance(PdfWriter writer) {
302 return new PdfReaderInstance(this, writer);
303 }
304
305 /** Gets the number of pages in the document.
306 * @return the number of pages in the document
307 */
308 public int getNumberOfPages() {
309 return pageRefs.size();
310 }
311
312 /** Returns the document's catalog. This dictionary is not a copy,
313 * any changes will be reflected in the catalog.
314 * @return the document's catalog
315 */
316 public PdfDictionary getCatalog() {
317 return catalog;
318 }
319
320 /** Returns the document's acroform, if it has one.
321 * @return the document's acroform
322 */
323 public PRAcroForm getAcroForm() {
324 if (!acroFormParsed) {
325 acroFormParsed = true;
326 PdfObject form = catalog.get(PdfName.ACROFORM);
327 if (form != null) {
328 try {
329 acroForm = new PRAcroForm(this);
330 acroForm.readAcroForm((PdfDictionary)getPdfObject(form));
331 }
332 catch (Exception e) {
333 acroForm = null;
334 }
335 }
336 }
337 return acroForm;
338 }
339 /**
340 * Gets the page rotation. This value can be 0, 90, 180 or 270.
341 * @param index the page number. The first page is 1
342 * @return the page rotation
343 */
344 public int getPageRotation(int index) {
345 return getPageRotation(pageRefs.getPageNRelease(index));
346 }
347
348 int getPageRotation(PdfDictionary page) {
349 PdfNumber rotate = (PdfNumber)getPdfObject(page.get(PdfName.ROTATE));
350 if (rotate == null)
351 return 0;
352 else {
353 int n = rotate.intValue();
354 n %= 360;
355 return n < 0 ? n + 360 : n;
356 }
357 }
358 /** Gets the page size, taking rotation into account. This
359 * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key.
360 * @param index the page number. The first page is 1
361 * @return a <CODE>Rectangle</CODE>
362 */
363 public Rectangle getPageSizeWithRotation(int index) {
364 return getPageSizeWithRotation(pageRefs.getPageNRelease(index));
365 }
366
367 /**
368 * Gets the rotated page from a page dictionary.
369 * @param page the page dictionary
370 * @return the rotated page
371 */
372 public Rectangle getPageSizeWithRotation(PdfDictionary page) {
373 Rectangle rect = getPageSize(page);
374 int rotation = getPageRotation(page);
375 while (rotation > 0) {
376 rect = rect.rotate();
377 rotation -= 90;
378 }
379 return rect;
380 }
381
382 /** Gets the page size without taking rotation into account. This
383 * is the value of the /MediaBox key.
384 * @param index the page number. The first page is 1
385 * @return the page size
386 */
387 public Rectangle getPageSize(int index) {
388 return getPageSize(pageRefs.getPageNRelease(index));
389 }
390
391 /**
392 * Gets the page from a page dictionary
393 * @param page the page dictionary
394 * @return the page
395 */
396 public Rectangle getPageSize(PdfDictionary page) {
397 PdfArray mediaBox = (PdfArray)getPdfObject(page.get(PdfName.MEDIABOX));
398 return getNormalizedRectangle(mediaBox);
399 }
400
401 /** Gets the crop box without taking rotation into account. This
402 * is the value of the /CropBox key. The crop box is the part
403 * of the document to be displayed or printed. It usually is the same
404 * as the media box but may be smaller. If the page doesn't have a crop
405 * box the page size will be returned.
406 * @param index the page number. The first page is 1
407 * @return the crop box
408 */
409 public Rectangle getCropBox(int index) {
410 PdfDictionary page = pageRefs.getPageNRelease(index);
411 PdfArray cropBox = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
412 if (cropBox == null)
413 return getPageSize(page);
414 return getNormalizedRectangle(cropBox);
415 }
416
417 /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media".
418 * @param index the page number. The first page is 1
419 * @param boxName the box name
420 * @return the box rectangle or null
421 */
422 public Rectangle getBoxSize(int index, String boxName) {
423 PdfDictionary page = pageRefs.getPageNRelease(index);
424 PdfArray box = null;
425 if (boxName.equals("trim"))
426 box = (PdfArray)getPdfObjectRelease(page.get(PdfName.TRIMBOX));
427 else if (boxName.equals("art"))
428 box = (PdfArray)getPdfObjectRelease(page.get(PdfName.ARTBOX));
429 else if (boxName.equals("bleed"))
430 box = (PdfArray)getPdfObjectRelease(page.get(PdfName.BLEEDBOX));
431 else if (boxName.equals("crop"))
432 box = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
433 else if (boxName.equals("media"))
434 box = (PdfArray)getPdfObjectRelease(page.get(PdfName.MEDIABOX));
435 if (box == null)
436 return null;
437 return getNormalizedRectangle(box);
438 }
439
440 /** Returns the content of the document information dictionary as a <CODE>HashMap</CODE>
441 * of <CODE>String</CODE>.
442 * @return content of the document information dictionary
443 */
444 public HashMap getInfo() {
445 HashMap map = new HashMap();
446 PdfDictionary info = (PdfDictionary)getPdfObject(trailer.get(PdfName.INFO));
447 if (info == null)
448 return map;
449 for (Iterator it = info.getKeys().iterator(); it.hasNext();) {
450 PdfName key = (PdfName)it.next();
451 PdfObject obj = getPdfObject(info.get(key));
452 if (obj == null)
453 continue;
454 String value = obj.toString();
455 switch (obj.type()) {
456 case PdfObject.STRING: {
457 value = ((PdfString)obj).toUnicodeString();
458 break;
459 }
460 case PdfObject.NAME: {
461 value = PdfName.decodeName(value);
462 break;
463 }
464 }
465 map.put(PdfName.decodeName(key.toString()), value);
466 }
467 return map;
468 }
469
470 /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury.
471 * @param box the original rectangle
472 * @return a normalized <CODE>Rectangle</CODE>
473 */
474 public static Rectangle getNormalizedRectangle(PdfArray box) {
475 ArrayList rect = box.getArrayList();
476 float llx = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(0))).floatValue();
477 float lly = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(1))).floatValue();
478 float urx = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(2))).floatValue();
479 float ury = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(3))).floatValue();
480 return new Rectangle(Math.min(llx, urx), Math.min(lly, ury),
481 Math.max(llx, urx), Math.max(lly, ury));
482 }
483
484 protected void readPdf() throws IOException {
485 try {
486 fileLength = tokens.getFile().length();
487 pdfVersion = tokens.checkPdfHeader();
488 try {
489 readXref();
490 }
491 catch (Exception e) {
492 try {
493 rebuilt = true;
494 rebuildXref();
495 lastXref = -1;
496 }
497 catch (Exception ne) {
498 throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
499 }
500 }
501 try {
502 readDocObj();
503 }
504 catch (Exception ne) {
505 if (rebuilt || encryptionError)
506 throw new IOException(ne.getMessage());
507 rebuilt = true;
508 encrypted = false;
509 rebuildXref();
510 lastXref = -1;
511 readDocObj();
512 }
513
514 strings.clear();
515 readPages();
516 eliminateSharedStreams();
517 removeUnusedObjects();
518 }
519 finally {
520 try {
521 tokens.close();
522 }
523 catch (Exception e) {
524 // empty on purpose
525 }
526 }
527 }
528
529 protected void readPdfPartial() throws IOException {
530 try {
531 fileLength = tokens.getFile().length();
532 pdfVersion = tokens.checkPdfHeader();
533 try {
534 readXref();
535 }
536 catch (Exception e) {
537 try {
538 rebuilt = true;
539 rebuildXref();
540 lastXref = -1;
541 }
542 catch (Exception ne) {
543 throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
544 }
545 }
546 readDocObjPartial();
547 readPages();
548 }
549 catch (IOException e) {
550 try{tokens.close();}catch(Exception ee){}
551 throw e;
552 }
553 }
554
555 private boolean equalsArray(byte ar1[], byte ar2[], int size) {
556 for (int k = 0; k < size; ++k) {
557 if (ar1[k] != ar2[k])
558 return false;
559 }
560 return true;
561 }
562
563 /**
564 * @throws IOException
565 */
566 private void readDecryptedDocObj() throws IOException {
567 if (encrypted)
568 return;
569 PdfObject encDic = trailer.get(PdfName.ENCRYPT);
570 if (encDic == null || encDic.toString().equals("null"))
571 return;
572 encryptionError = true;
573 byte[] encryptionKey = null;
574 encrypted = true;
575 PdfDictionary enc = (PdfDictionary)getPdfObject(encDic);
576
577 String s;
578 PdfObject o;
579
580 PdfArray documentIDs = (PdfArray)getPdfObject(trailer.get(PdfName.ID));
581 byte documentID[] = null;
582 if (documentIDs != null) {
583 o = (PdfObject)documentIDs.getArrayList().get(0);
584 strings.remove(o);
585 s = o.toString();
586 documentID = com.lowagie.text.DocWriter.getISOBytes(s);
587 if (documentIDs.size() > 1)
588 strings.remove(documentIDs.getArrayList().get(1));
589 }
590 // just in case we have a broken producer
591 if (documentID == null)
592 documentID = new byte[0];
593 byte uValue[] = null;
594 byte oValue[] = null;
595 int cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
596 int lengthValue = 0;
597
598 PdfObject filter = getPdfObjectRelease(enc.get(PdfName.FILTER));
599
600 if (filter.equals(PdfName.STANDARD))
601 {
602 s = enc.get(PdfName.U).toString();
603 strings.remove(enc.get(PdfName.U));
604 uValue = com.lowagie.text.DocWriter.getISOBytes(s);
605 s = enc.get(PdfName.O).toString();
606 strings.remove(enc.get(PdfName.O));
607 oValue = com.lowagie.text.DocWriter.getISOBytes(s);
608
609 o = enc.get(PdfName.R);
610 if (!o.isNumber()) throw new IOException("Illegal R value.");
611 rValue = ((PdfNumber)o).intValue();
612 if (rValue != 2 && rValue != 3 && rValue != 4)
613 throw new IOException("Unknown encryption type (" + rValue + ")");
614
615 o = enc.get(PdfName.P);
616 if (!o.isNumber()) throw new IOException("Illegal P value.");
617 pValue = ((PdfNumber)o).intValue();
618
619 if ( rValue == 3 ){
620 o = enc.get(PdfName.LENGTH);
621 if (!o.isNumber())
622 throw new IOException("Illegal Length value.");
623 lengthValue = ( (PdfNumber) o).intValue();
624 if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
625 throw new IOException("Illegal Length value.");
626 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
627 } else if (rValue == 4) {
628 PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
629 if (dic == null)
630 throw new IOException("/CF not found (encryption)");
631 dic = (PdfDictionary)dic.get(PdfName.STDCF);
632 if (dic == null)
633 throw new IOException("/StdCF not found (encryption)");
634 if (PdfName.V2.equals(dic.get(PdfName.CFM)))
635 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
636 else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
637 cryptoMode = PdfWriter.ENCRYPTION_AES_128;
638 else
639 throw new IOException("No compatible encryption found");
640 PdfObject em = enc.get(PdfName.ENCRYPTMETADATA);
641 if (em != null && em.toString().equals("false"))
642 cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
643 } else {
644 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
645 }
646 } else if (filter.equals(PdfName.PUBSEC)) {
647 boolean foundRecipient = false;
648 byte[] envelopedData = null;
649 PdfArray recipients = null;
650
651 o = enc.get(PdfName.V);
652 if (!o.isNumber()) throw new IOException("Illegal V value.");
653 int vValue = ((PdfNumber)o).intValue();
654 if (vValue != 1 && vValue != 2 && vValue != 4)
655 throw new IOException("Unknown encryption type V = " + rValue);
656
657 if ( vValue == 2 ){
658 o = enc.get(PdfName.LENGTH);
659 if (!o.isNumber())
660 throw new IOException("Illegal Length value.");
661 lengthValue = ( (PdfNumber) o).intValue();
662 if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
663 throw new IOException("Illegal Length value.");
664 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
665 recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);
666 } else if (vValue == 4) {
667 PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
668 if (dic == null)
669 throw new IOException("/CF not found (encryption)");
670 dic = (PdfDictionary)dic.get(PdfName.DEFAULTCRYPTFILER);
671 if (dic == null)
672 throw new IOException("/DefaultCryptFilter not found (encryption)");
673 if (PdfName.V2.equals(dic.get(PdfName.CFM)))
674 {
675 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
676 lengthValue = 128;
677 }
678 else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
679 {
680 cryptoMode = PdfWriter.ENCRYPTION_AES_128;
681 lengthValue = 128;
682 }
683 else
684 throw new IOException("No compatible encryption found");
685 PdfObject em = dic.get(PdfName.ENCRYPTMETADATA);
686 if (em != null && em.toString().equals("false"))
687 cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
688
689 recipients = (PdfArray)dic.get(PdfName.RECIPIENTS);
690 } else {
691 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
692 lengthValue = 40;
693 recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);
694 }
695
696 for (int i = 0; i<recipients.size(); i++)
697 {
698 PdfObject recipient = (PdfObject)recipients.getArrayList().get(i);
699 strings.remove(recipient);
700
701 CMSEnvelopedData data = null;
702 try {
703 data = new CMSEnvelopedData(recipient.getBytes());
704
705 Iterator recipientCertificatesIt =
706 data.getRecipientInfos().getRecipients().iterator();
707
708 while (recipientCertificatesIt.hasNext()) {
709 RecipientInformation recipientInfo =
710 (RecipientInformation)recipientCertificatesIt.next();
711
712 if (recipientInfo.getRID().match(certificate) && !foundRecipient) {
713
714 envelopedData = recipientInfo.getContent(certificateKey, certificateKeyProvider);
715 foundRecipient = true;
716 }
717 }
718 } catch (Exception f) {
719 throw new ExceptionConverter(f);
720 }
721 }
722
723 if(!foundRecipient || envelopedData == null)
724 {
725 throw new IOException("Bad certificate and key.");
726 }
727
728 MessageDigest md = null;
729
730 try {
731 md = MessageDigest.getInstance("SHA-1");
732 md.update(envelopedData, 0, 20);
733 for (int i=0; i<recipients.size(); i++)
734 {
735 byte[] encodedRecipient = ((PdfObject)recipients.getArrayList().get(i)).getBytes();
736 md.update(encodedRecipient);
737 }
738 if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
739 md.update(new byte[]{(byte)255, (byte)255, (byte)255, (byte)255});
740 encryptionKey = md.digest();
741
742 } catch (Exception f) {
743 throw new ExceptionConverter(f);
744 }
745 }
746
747
748 decrypt = new PdfEncryption();
749 decrypt.setCryptoMode(cryptoMode, lengthValue);
750
751 if (filter.equals(PdfName.STANDARD))
752 {
753 //check by owner password
754 decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue);
755 if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
756 //check by user password
757 decrypt.setupByUserPassword(documentID, password, oValue, pValue);
758 if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
759 throw new BadPasswordException();
760 }
761 }
762 else
763 ownerPasswordUsed = true;
764 } else if (filter.equals(PdfName.PUBSEC)) {
765 decrypt.setupByEncryptionKey(encryptionKey, lengthValue);
766 ownerPasswordUsed = true;
767 }
768
769 for (int k = 0; k < strings.size(); ++k) {
770 PdfString str = (PdfString)strings.get(k);
771 str.decrypt(this);
772 }
773
774 if (encDic.isIndirect()) {
775 cryptoRef = (PRIndirectReference)encDic;
776 xrefObj.set(cryptoRef.getNumber(), null);
777 }
778 encryptionError = false;
779 }
780
781 /**
782 * @param obj
783 * @return a PdfObject
784 */
785 public static PdfObject getPdfObjectRelease(PdfObject obj) {
786 PdfObject obj2 = getPdfObject(obj);
787 releaseLastXrefPartial(obj);
788 return obj2;
789 }
790
791
792 /**
793 * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
794 * if needed.
795 * @param obj the <CODE>PdfObject</CODE> to read
796 * @return the resolved <CODE>PdfObject</CODE>
797 */
798 public static PdfObject getPdfObject(PdfObject obj) {
799 if (obj == null)
800 return null;
801 if (!obj.isIndirect())
802 return obj;
803 try {
804 PRIndirectReference ref = (PRIndirectReference)obj;
805 int idx = ref.getNumber();
806 boolean appendable = ref.getReader().appendable;
807 obj = ref.getReader().getPdfObject(idx);
808 if (obj == null) {
809 return null;
810 }
811 else {
812 if (appendable) {
813 switch (obj.type()) {
814 case PdfObject.NULL:
815 obj = new PdfNull();
816 break;
817 case PdfObject.BOOLEAN:
818 obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
819 break;
820 case PdfObject.NAME:
821 obj = new PdfName(obj.getBytes());
822 break;
823 }
824 obj.setIndRef(ref);
825 }
826 return obj;
827 }
828 }
829 catch (Exception e) {
830 throw new ExceptionConverter(e);
831 }
832 }
833
834 /**
835 * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
836 * if needed. If the reader was opened in partial mode the object will be released
837 * to save memory.
838 * @param obj the <CODE>PdfObject</CODE> to read
839 * @param parent
840 * @return a PdfObject
841 */
842 public static PdfObject getPdfObjectRelease(PdfObject obj, PdfObject parent) {
843 PdfObject obj2 = getPdfObject(obj, parent);
844 releaseLastXrefPartial(obj);
845 return obj2;
846 }
847
848 /**
849 * @param obj
850 * @param parent
851 * @return a PdfObject
852 */
853 public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
854 if (obj == null)
855 return null;
856 if (!obj.isIndirect()) {
857 PRIndirectReference ref = null;
858 if (parent != null && (ref = parent.getIndRef()) != null && ref.getReader().isAppendable()) {
859 switch (obj.type()) {
860 case PdfObject.NULL:
861 obj = new PdfNull();
862 break;
863 case PdfObject.BOOLEAN:
864 obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
865 break;
866 case PdfObject.NAME:
867 obj = new PdfName(obj.getBytes());
868 break;
869 }
870 obj.setIndRef(ref);
871 }
872 return obj;
873 }
874 return getPdfObject(obj);
875 }
876
877 /**
878 * @param idx
879 * @return a PdfObject
880 */
881 public PdfObject getPdfObjectRelease(int idx) {
882 PdfObject obj = getPdfObject(idx);
883 releaseLastXrefPartial();
884 return obj;
885 }
886
887 /**
888 * @param idx
889 * @return aPdfObject
890 */
891 public PdfObject getPdfObject(int idx) {
892 try {
893 lastXrefPartial = -1;
894 if (idx < 0 || idx >= xrefObj.size())
895 return null;
896 PdfObject obj = (PdfObject)xrefObj.get(idx);
897 if (!partial || obj != null)
898 return obj;
899 if (idx * 2 >= xref.length)
900 return null;
901 obj = readSingleObject(idx);
902 lastXrefPartial = -1;
903 if (obj != null)
904 lastXrefPartial = idx;
905 return obj;
906 }
907 catch (Exception e) {
908 throw new ExceptionConverter(e);
909 }
910 }
911
912 /**
913 *
914 */
915 public void resetLastXrefPartial() {
916 lastXrefPartial = -1;
917 }
918
919 /**
920 *
921 */
922 public void releaseLastXrefPartial() {
923 if (partial && lastXrefPartial != -1) {
924 xrefObj.set(lastXrefPartial, null);
925 lastXrefPartial = -1;
926 }
927 }
928
929 /**
930 * @param obj
931 */
932 public static void releaseLastXrefPartial(PdfObject obj) {
933 if (obj == null)
934 return;
935 if (!obj.isIndirect())
936 return;
937 PRIndirectReference ref = (PRIndirectReference)obj;
938 PdfReader reader = ref.getReader();
939 if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == ref.getNumber()) {
940 reader.xrefObj.set(reader.lastXrefPartial, null);
941 }
942 reader.lastXrefPartial = -1;
943 }
944
945 private void setXrefPartialObject(int idx, PdfObject obj) {
946 if (!partial || idx < 0)
947 return;
948 xrefObj.set(idx, obj);
949 }
950
951 /**
952 * @param obj
953 * @return an indirect reference
954 */
955 public PRIndirectReference addPdfObject(PdfObject obj) {
956 xrefObj.add(obj);
957 return new PRIndirectReference(this, xrefObj.size() - 1);
958 }
959
960 protected void readPages() throws IOException {
961 catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
962 rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
963 pageRefs = new PageRefs(this);
964 }
965
966 protected void readDocObjPartial() throws IOException {
967 xrefObj = new ArrayList(xref.length / 2);
968 xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
969 readDecryptedDocObj();
970 if (objStmToOffset != null) {
971 int keys[] = objStmToOffset.getKeys();
972 for (int k = 0; k < keys.length; ++k) {
973 int n = keys[k];
974 objStmToOffset.put(n, xref[n * 2]);
975 xref[n * 2] = -1;
976 }
977 }
978 }
979
980 protected PdfObject readSingleObject(int k) throws IOException {
981 strings.clear();
982 int k2 = k * 2;
983 int pos = xref[k2];
984 if (pos < 0)
985 return null;
986 if (xref[k2 + 1] > 0)
987 pos = objStmToOffset.get(xref[k2 + 1]);
988 if (pos == 0)
989 return null;
990 tokens.seek(pos);
991 tokens.nextValidToken();
992 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
993 tokens.throwError("Invalid object number.");
994 objNum = tokens.intValue();
995 tokens.nextValidToken();
996 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
997 tokens.throwError("Invalid generation number.");
998 objGen = tokens.intValue();
999 tokens.nextValidToken();
1000 if (!tokens.getStringValue().equals("obj"))
1001 tokens.throwError("Token 'obj' expected.");
1002 PdfObject obj;
1003 try {
1004 obj = readPRObject();
1005 for (int j = 0; j < strings.size(); ++j) {
1006 PdfString str = (PdfString)strings.get(j);
1007 str.decrypt(this);
1008 }
1009 if (obj.isStream()) {
1010 checkPRStreamLength((PRStream)obj);
1011 }
1012 }
1013 catch (Exception e) {
1014 obj = null;
1015 }
1016 if (xref[k2 + 1] > 0) {
1017 obj = readOneObjStm((PRStream)obj, xref[k2]);
1018 }
1019 xrefObj.set(k, obj);
1020 return obj;
1021 }
1022
1023 protected PdfObject readOneObjStm(PRStream stream, int idx) throws IOException {
1024 int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
1025 byte b[] = getStreamBytes(stream, tokens.getFile());
1026 PRTokeniser saveTokens = tokens;
1027 tokens = new PRTokeniser(b);
1028 try {
1029 int address = 0;
1030 boolean ok = true;
1031 ++idx;
1032 for (int k = 0; k < idx; ++k) {
1033 ok = tokens.nextToken();
1034 if (!ok)
1035 break;
1036 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1037 ok = false;
1038 break;
1039 }
1040 ok = tokens.nextToken();
1041 if (!ok)
1042 break;
1043 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1044 ok = false;
1045 break;
1046 }
1047 address = tokens.intValue() + first;
1048 }
1049 if (!ok)
1050 throw new IOException("Error reading ObjStm");
1051 tokens.seek(address);
1052 return readPRObject();
1053 }
1054 finally {
1055 tokens = saveTokens;
1056 }
1057 }
1058
1059 /**
1060 * @return the percentage of the cross reference table that has been read
1061 */
1062 public double dumpPerc() {
1063 int total = 0;
1064 for (int k = 0; k < xrefObj.size(); ++k) {
1065 if (xrefObj.get(k) != null)
1066 ++total;
1067 }
1068 return (total * 100.0 / xrefObj.size());
1069 }
1070
1071 protected void readDocObj() throws IOException {
1072 ArrayList streams = new ArrayList();
1073 xrefObj = new ArrayList(xref.length / 2);
1074 xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
1075 for (int k = 2; k < xref.length; k += 2) {
1076 int pos = xref[k];
1077 if (pos <= 0 || xref[k + 1] > 0)
1078 continue;
1079 tokens.seek(pos);
1080 tokens.nextValidToken();
1081 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1082 tokens.throwError("Invalid object number.");
1083 objNum = tokens.intValue();
1084 tokens.nextValidToken();
1085 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1086 tokens.throwError("Invalid generation number.");
1087 objGen = tokens.intValue();
1088 tokens.nextValidToken();
1089 if (!tokens.getStringValue().equals("obj"))
1090 tokens.throwError("Token 'obj' expected.");
1091 PdfObject obj;
1092 try {
1093 obj = readPRObject();
1094 if (obj.isStream()) {
1095 streams.add(obj);
1096 }
1097 }
1098 catch (Exception e) {
1099 obj = null;
1100 }
1101 xrefObj.set(k / 2, obj);
1102 }
1103 for (int k = 0; k < streams.size(); ++k) {
1104 checkPRStreamLength((PRStream)streams.get(k));
1105 }
1106 readDecryptedDocObj();
1107 if (objStmMark != null) {
1108 for (Iterator i = objStmMark.entrySet().iterator(); i.hasNext();) {
1109 Map.Entry entry = (Map.Entry)i.next();
1110 int n = ((Integer)entry.getKey()).intValue();
1111 IntHashtable h = (IntHashtable)entry.getValue();
1112 readObjStm((PRStream)xrefObj.get(n), h);
1113 xrefObj.set(n, null);
1114 }
1115 objStmMark = null;
1116 }
1117 xref = null;
1118 }
1119
1120 private void checkPRStreamLength(PRStream stream) throws IOException {
1121 int fileLength = tokens.length();
1122 int start = stream.getOffset();
1123 boolean calc = false;
1124 int streamLength = 0;
1125 PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH));
1126 if (obj != null && obj.type() == PdfObject.NUMBER) {
1127 streamLength = ((PdfNumber)obj).intValue();
1128 if (streamLength + start > fileLength - 20)
1129 calc = true;
1130 else {
1131 tokens.seek(start + streamLength);
1132 String line = tokens.readString(20);
1133 if (!line.startsWith("\nendstream") &&
1134 !line.startsWith("\r\nendstream") &&
1135 !line.startsWith("\rendstream") &&
1136 !line.startsWith("endstream"))
1137 calc = true;
1138 }
1139 }
1140 else
1141 calc = true;
1142 if (calc) {
1143 byte tline[] = new byte[16];
1144 tokens.seek(start);
1145 while (true) {
1146 int pos = tokens.getFilePointer();
1147 if (!tokens.readLineSegment(tline))
1148 break;
1149 if (equalsn(tline, endstream)) {
1150 streamLength = pos - start;
1151 break;
1152 }
1153 if (equalsn(tline, endobj)) {
1154 tokens.seek(pos - 16);
1155 String s = tokens.readString(16);
1156 int index = s.indexOf("endstream");
1157 if (index >= 0)
1158 pos = pos - 16 + index;
1159 streamLength = pos - start;
1160 break;
1161 }
1162 }
1163 }
1164 stream.setLength(streamLength);
1165 }
1166
1167 protected void readObjStm(PRStream stream, IntHashtable map) throws IOException {
1168 int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
1169 int n = ((PdfNumber)getPdfObject(stream.get(PdfName.N))).intValue();
1170 byte b[] = getStreamBytes(stream, tokens.getFile());
1171 PRTokeniser saveTokens = tokens;
1172 tokens = new PRTokeniser(b);
1173 try {
1174 int address[] = new int[n];
1175 int objNumber[] = new int[n];
1176 boolean ok = true;
1177 for (int k = 0; k < n; ++k) {
1178 ok = tokens.nextToken();
1179 if (!ok)
1180 break;
1181 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1182 ok = false;
1183 break;
1184 }
1185 objNumber[k] = tokens.intValue();
1186 ok = tokens.nextToken();
1187 if (!ok)
1188 break;
1189 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1190 ok = false;
1191 break;
1192 }
1193 address[k] = tokens.intValue() + first;
1194 }
1195 if (!ok)
1196 throw new IOException("Error reading ObjStm");
1197 for (int k = 0; k < n; ++k) {
1198 if (map.containsKey(k)) {
1199 tokens.seek(address[k]);
1200 PdfObject obj = readPRObject();
1201 xrefObj.set(objNumber[k], obj);
1202 }
1203 }
1204 }
1205 finally {
1206 tokens = saveTokens;
1207 }
1208 }
1209
1210 /**
1211 * Eliminates the reference to the object freeing the memory used by it and clearing
1212 * the xref entry.
1213 * @param obj the object. If it's an indirect reference it will be eliminated
1214 * @return the object or the already erased dereferenced object
1215 */
1216 public static PdfObject killIndirect(PdfObject obj) {
1217 if (obj == null || obj.isNull())
1218 return null;
1219 PdfObject ret = getPdfObjectRelease(obj);
1220 if (obj.isIndirect()) {
1221 PRIndirectReference ref = (PRIndirectReference)obj;
1222 PdfReader reader = ref.getReader();
1223 int n = ref.getNumber();
1224 reader.xrefObj.set(n, null);
1225 if (reader.partial)
1226 reader.xref[n * 2] = -1;
1227 }
1228 return ret;
1229 }
1230
1231 private void ensureXrefSize(int size) {
1232 if (size == 0)
1233 return;
1234 if (xref == null)
1235 xref = new int[size];
1236 else {
1237 if (xref.length < size) {
1238 int xref2[] = new int[size];
1239 System.arraycopy(xref, 0, xref2, 0, xref.length);
1240 xref = xref2;
1241 }
1242 }
1243 }
1244
1245 protected void readXref() throws IOException {
1246 hybridXref = false;
1247 newXrefType = false;
1248 tokens.seek(tokens.getStartxref());
1249 tokens.nextToken();
1250 if (!tokens.getStringValue().equals("startxref"))
1251 throw new IOException("startxref not found.");
1252 tokens.nextToken();
1253 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1254 throw new IOException("startxref is not followed by a number.");
1255 int startxref = tokens.intValue();
1256 lastXref = startxref;
1257 eofPos = tokens.getFilePointer();
1258 try {
1259 if (readXRefStream(startxref)) {
1260 newXrefType = true;
1261 return;
1262 }
1263 }
1264 catch (Exception e) {}
1265 xref = null;
1266 tokens.seek(startxref);
1267 trailer = readXrefSection();
1268 PdfDictionary trailer2 = trailer;
1269 while (true) {
1270 PdfNumber prev = (PdfNumber)trailer2.get(PdfName.PREV);
1271 if (prev == null)
1272 break;
1273 tokens.seek(prev.intValue());
1274 trailer2 = readXrefSection();
1275 }
1276 }
1277
1278 protected PdfDictionary readXrefSection() throws IOException {
1279 tokens.nextValidToken();
1280 if (!tokens.getStringValue().equals("xref"))
1281 tokens.throwError("xref subsection not found");
1282 int start = 0;
1283 int end = 0;
1284 int pos = 0;
1285 int gen = 0;
1286 while (true) {
1287 tokens.nextValidToken();
1288 if (tokens.getStringValue().equals("trailer"))
1289 break;
1290 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1291 tokens.throwError("Object number of the first object in this xref subsection not found");
1292 start = tokens.intValue();
1293 tokens.nextValidToken();
1294 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1295 tokens.throwError("Number of entries in this xref subsection not found");
1296 end = tokens.intValue() + start;
1297 if (start == 1) { // fix incorrect start number
1298 int back = tokens.getFilePointer();
1299 tokens.nextValidToken();
1300 pos = tokens.intValue();
1301 tokens.nextValidToken();
1302 gen = tokens.intValue();
1303 if (pos == 0 && gen == 65535) {
1304 --start;
1305 --end;
1306 }
1307 tokens.seek(back);
1308 }
1309 ensureXrefSize(end * 2);
1310 for (int k = start; k < end; ++k) {
1311 tokens.nextValidToken();
1312 pos = tokens.intValue();
1313 tokens.nextValidToken();
1314 gen = tokens.intValue();
1315 tokens.nextValidToken();
1316 int p = k * 2;
1317 if (tokens.getStringValue().equals("n")) {
1318 if (xref[p] == 0 && xref[p + 1] == 0) {
1319 // if (pos == 0)
1320 // tokens.throwError("File position 0 cross-reference entry in this xref subsection");
1321 xref[p] = pos;
1322 }
1323 }
1324 else if (tokens.getStringValue().equals("f")) {
1325 if (xref[p] == 0 && xref[p + 1] == 0)
1326 xref[p] = -1;
1327 }
1328 else
1329 tokens.throwError("Invalid cross-reference entry in this xref subsection");
1330 }
1331 }
1332 PdfDictionary trailer = (PdfDictionary)readPRObject();
1333 PdfNumber xrefSize = (PdfNumber)trailer.get(PdfName.SIZE);
1334 ensureXrefSize(xrefSize.intValue() * 2);
1335 PdfObject xrs = trailer.get(PdfName.XREFSTM);
1336 if (xrs != null && xrs.isNumber()) {
1337 int loc = ((PdfNumber)xrs).intValue();
1338 try {
1339 readXRefStream(loc);
1340 newXrefType = true;
1341 hybridXref = true;
1342 }
1343 catch (IOException e) {
1344 xref = null;
1345 throw e;
1346 }
1347 }
1348 return trailer;
1349 }
1350
1351 protected boolean readXRefStream(int ptr) throws IOException {
1352 tokens.seek(ptr);
1353 int thisStream = 0;
1354 if (!tokens.nextToken())
1355 return false;
1356 if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1357 return false;
1358 thisStream = tokens.intValue();
1359 if (!tokens.nextToken() || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1360 return false;
1361 if (!tokens.nextToken() || !tokens.getStringValue().equals("obj"))
1362 return false;
1363 PdfObject object = readPRObject();
1364 PRStream stm = null;
1365 if (object.isStream()) {
1366 stm = (PRStream)object;
1367 if (!PdfName.XREF.equals(stm.get(PdfName.TYPE)))
1368 return false;
1369 }
1370 else
1371 return false;
1372 if (trailer == null) {
1373 trailer = new PdfDictionary();
1374 trailer.putAll(stm);
1375 }
1376 stm.setLength(((PdfNumber)stm.get(PdfName.LENGTH)).intValue());
1377 int size = ((PdfNumber)stm.get(PdfName.SIZE)).intValue();
1378 PdfArray index;
1379 PdfObject obj = stm.get(PdfName.INDEX);
1380 if (obj == null) {
1381 index = new PdfArray();
1382 index.add(new int[]{0, size});
1383 }
1384 else
1385 index = (PdfArray)obj;
1386 PdfArray w = (PdfArray)stm.get(PdfName.W);
1387 int prev = -1;
1388 obj = stm.get(PdfName.PREV);
1389 if (obj != null)
1390 prev = ((PdfNumber)obj).intValue();
1391 // Each xref pair is a position
1392 // type 0 -> -1, 0
1393 // type 1 -> offset, 0
1394 // type 2 -> index, obj num
1395 ensureXrefSize(size * 2);
1396 if (objStmMark == null && !partial)
1397 objStmMark = new HashMap();
1398 if (objStmToOffset == null && partial)
1399 objStmToOffset = new IntHashtable();
1400 byte b[] = getStreamBytes(stm, tokens.getFile());
1401 int bptr = 0;
1402 ArrayList wa = w.getArrayList();
1403 int wc[] = new int[3];
1404 for (int k = 0; k < 3; ++k)
1405 wc[k] = ((PdfNumber)wa.get(k)).intValue();
1406 ArrayList sections = index.getArrayList();
1407 for (int idx = 0; idx < sections.size(); idx += 2) {
1408 int start = ((PdfNumber)sections.get(idx)).intValue();
1409 int length = ((PdfNumber)sections.get(idx + 1)).intValue();
1410 ensureXrefSize((start + length) * 2);
1411 while (length-- > 0) {
1412 int type = 1;
1413 if (wc[0] > 0) {
1414 type = 0;
1415 for (int k = 0; k < wc[0]; ++k)
1416 type = (type << 8) + (b[bptr++] & 0xff);
1417 }
1418 int field2 = 0;
1419 for (int k = 0; k < wc[1]; ++k)
1420 field2 = (field2 << 8) + (b[bptr++] & 0xff);
1421 int field3 = 0;
1422 for (int k = 0; k < wc[2]; ++k)
1423 field3 = (field3 << 8) + (b[bptr++] & 0xff);
1424 int base = start * 2;
1425 if (xref[base] == 0 && xref[base + 1] == 0) {
1426 switch (type) {
1427 case 0:
1428 xref[base] = -1;
1429 break;
1430 case 1:
1431 xref[base] = field2;
1432 break;
1433 case 2:
1434 xref[base] = field3;
1435 xref[base + 1] = field2;
1436 if (partial) {
1437 objStmToOffset.put(field2, 0);
1438 }
1439 else {
1440 Integer on = new Integer(field2);
1441 IntHashtable seq = (IntHashtable)objStmMark.get(on);
1442 if (seq == null) {
1443 seq = new IntHashtable();
1444 seq.put(field3, 1);
1445 objStmMark.put(on, seq);
1446 }
1447 else
1448 seq.put(field3, 1);
1449 }
1450 break;
1451 }
1452 }
1453 ++start;
1454 }
1455 }
1456 thisStream *= 2;
1457 if (thisStream < xref.length)
1458 xref[thisStream] = -1;
1459
1460 if (prev == -1)
1461 return true;
1462 return readXRefStream(prev);
1463 }
1464
1465 protected void rebuildXref() throws IOException {
1466 hybridXref = false;
1467 newXrefType = false;
1468 tokens.seek(0);
1469 int xr[][] = new int[1024][];
1470 int top = 0;
1471 trailer = null;
1472 byte line[] = new byte[64];
1473 for (;;) {
1474 int pos = tokens.getFilePointer();
1475 if (!tokens.readLineSegment(line))
1476 break;
1477 if (line[0] == 't') {
1478 if (!PdfEncodings.convertToString(line, null).startsWith("trailer"))
1479 continue;
1480 tokens.seek(pos);
1481 tokens.nextToken();
1482 pos = tokens.getFilePointer();
1483 try {
1484 PdfDictionary dic = (PdfDictionary)readPRObject();
1485 if (dic.get(PdfName.ROOT) != null)
1486 trailer = dic;
1487 else
1488 tokens.seek(pos);
1489 }
1490 catch (Exception e) {
1491 tokens.seek(pos);
1492 }
1493 }
1494 else if (line[0] >= '0' && line[0] <= '9') {
1495 int obj[] = PRTokeniser.checkObjectStart(line);
1496 if (obj == null)
1497 continue;
1498 int num = obj[0];
1499 int gen = obj[1];
1500 if (num >= xr.length) {
1501 int newLength = num * 2;
1502 int xr2[][] = new int[newLength][];
1503 System.arraycopy(xr, 0, xr2, 0, top);
1504 xr = xr2;
1505 }
1506 if (num >= top)
1507 top = num + 1;
1508 if (xr[num] == null || gen >= xr[num][1]) {
1509 obj[0] = pos;
1510 xr[num] = obj;
1511 }
1512 }
1513 }
1514 if (trailer == null)
1515 throw new IOException("trailer not found.");
1516 xref = new int[top * 2];
1517 for (int k = 0; k < top; ++k) {
1518 int obj[] = xr[k];
1519 if (obj != null)
1520 xref[k * 2] = obj[0];
1521 }
1522 }
1523
1524 protected PdfDictionary readDictionary() throws IOException {
1525 PdfDictionary dic = new PdfDictionary();
1526 while (true) {
1527 tokens.nextValidToken();
1528 if (tokens.getTokenType() == PRTokeniser.TK_END_DIC)
1529 break;
1530 if (tokens.getTokenType() != PRTokeniser.TK_NAME)
1531 tokens.throwError("Dictionary key is not a name.");
1532 PdfName name = new PdfName(tokens.getStringValue(), false);
1533 PdfObject obj = readPRObject();
1534 int type = obj.type();
1535 if (-type == PRTokeniser.TK_END_DIC)
1536 tokens.throwError("Unexpected '>>'");
1537 if (-type == PRTokeniser.TK_END_ARRAY)
1538 tokens.throwError("Unexpected ']'");
1539 dic.put(name, obj);
1540 }
1541 return dic;
1542 }
1543
1544 protected PdfArray readArray() throws IOException {
1545 PdfArray array = new PdfArray();
1546 while (true) {
1547 PdfObject obj = readPRObject();
1548 int type = obj.type();
1549 if (-type == PRTokeniser.TK_END_ARRAY)
1550 break;
1551 if (-type == PRTokeniser.TK_END_DIC)
1552 tokens.throwError("Unexpected '>>'");
1553 array.add(obj);
1554 }
1555 return array;
1556 }
1557
1558 protected PdfObject readPRObject() throws IOException {
1559 tokens.nextValidToken();
1560 int type = tokens.getTokenType();
1561 switch (type) {
1562 case PRTokeniser.TK_START_DIC: {
1563 PdfDictionary dic = readDictionary();
1564 int pos = tokens.getFilePointer();
1565 // be careful in the trailer. May not be a "next" token.
1566 if (tokens.nextToken() && tokens.getStringValue().equals("stream")) {
1567 int ch = tokens.read();
1568 if (ch != '\n')
1569 ch = tokens.read();
1570 if (ch != '\n')
1571 tokens.backOnePosition(ch);
1572 PRStream stream = new PRStream(this, tokens.getFilePointer());
1573 stream.putAll(dic);
1574 stream.setObjNum(objNum, objGen);
1575 return stream;
1576 }
1577 else {
1578 tokens.seek(pos);
1579 return dic;
1580 }
1581 }
1582 case PRTokeniser.TK_START_ARRAY:
1583 return readArray();
1584 case PRTokeniser.TK_NUMBER:
1585 return new PdfNumber(tokens.getStringValue());
1586 case PRTokeniser.TK_STRING:
1587 PdfString str = new PdfString(tokens.getStringValue(), null).setHexWriting(tokens.isHexString());
1588 str.setObjNum(objNum, objGen);
1589 if (strings != null)
1590 strings.add(str);
1591 return str;
1592 case PRTokeniser.TK_NAME:
1593 return new PdfName(tokens.getStringValue(), false);
1594 case PRTokeniser.TK_REF:
1595 int num = tokens.getReference();
1596 PRIndirectReference ref = new PRIndirectReference(this, num, tokens.getGeneration());
1597 return ref;
1598 default:
1599 String sv = tokens.getStringValue();
1600 if ("null".equals(sv))
1601 return PdfNull.PDFNULL;
1602 else if ("true".equals(sv))
1603 return PdfBoolean.PDFTRUE;
1604 else if ("false".equals(sv))
1605 return PdfBoolean.PDFFALSE;
1606 return new PdfLiteral(-type, tokens.getStringValue());
1607 }
1608 }
1609
1610 /** Decodes a stream that has the FlateDecode filter.
1611 * @param in the input data
1612 * @return the decoded data
1613 */
1614 public static byte[] FlateDecode(byte in[]) {
1615 byte b[] = FlateDecode(in, true);
1616 if (b == null)
1617 return FlateDecode(in, false);
1618 return b;
1619 }
1620
1621 /**
1622 * @param in
1623 * @param dicPar
1624 * @return a byte array
1625 */
1626 public static byte[] decodePredictor(byte in[], PdfObject dicPar) {
1627 if (dicPar == null || !dicPar.isDictionary())
1628 return in;
1629 PdfDictionary dic = (PdfDictionary)dicPar;
1630 PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR));
1631 if (obj == null || !obj.isNumber())
1632 return in;
1633 int predictor = ((PdfNumber)obj).intValue();
1634 if (predictor < 10)
1635 return in;
1636 int width = 1;
1637 obj = getPdfObject(dic.get(PdfName.COLUMNS));
1638 if (obj != null && obj.isNumber())
1639 width = ((PdfNumber)obj).intValue();
1640 int colors = 1;
1641 obj = getPdfObject(dic.get(PdfName.COLORS));
1642 if (obj != null && obj.isNumber())
1643 colors = ((PdfNumber)obj).intValue();
1644 int bpc = 8;
1645 obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT));
1646 if (obj != null && obj.isNumber())
1647 bpc = ((PdfNumber)obj).intValue();
1648 DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(in));
1649 ByteArrayOutputStream fout = new ByteArrayOutputStream(in.length);
1650 int bytesPerPixel = colors * bpc / 8;
1651 int bytesPerRow = (colors*width*bpc + 7)/8;
1652 byte[] curr = new byte[bytesPerRow];
1653 byte[] prior = new byte[bytesPerRow];
1654
1655 // Decode the (sub)image row-by-row
1656 while (true) {
1657 // Read the filter type byte and a row of data
1658 int filter = 0;
1659 try {
1660 filter = dataStream.read();
1661 if (filter < 0) {
1662 return fout.toByteArray();
1663 }
1664 dataStream.readFully(curr, 0, bytesPerRow);
1665 } catch (Exception e) {
1666 return fout.toByteArray();
1667 }
1668
1669 switch (filter) {
1670 case 0: //PNG_FILTER_NONE
1671 break;
1672 case 1: //PNG_FILTER_SUB
1673 for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1674 curr[i] += curr[i - bytesPerPixel];
1675 }
1676 break;
1677 case 2: //PNG_FILTER_UP
1678 for (int i = 0; i < bytesPerRow; i++) {
1679 curr[i] += prior[i];
1680 }
1681 break;
1682 case 3: //PNG_FILTER_AVERAGE
1683 for (int i = 0; i < bytesPerPixel; i++) {
1684 curr[i] += prior[i] / 2;
1685 }
1686 for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1687 curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff))/2;
1688 }
1689 break;
1690 case 4: //PNG_FILTER_PAETH
1691 for (int i = 0; i < bytesPerPixel; i++) {
1692 curr[i] += prior[i];
1693 }
1694
1695 for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1696 int a = curr[i - bytesPerPixel] & 0xff;
1697 int b = prior[i] & 0xff;
1698 int c = prior[i - bytesPerPixel] & 0xff;
1699
1700 int p = a + b - c;
1701 int pa = Math.abs(p - a);
1702 int pb = Math.abs(p - b);
1703 int pc = Math.abs(p - c);
1704
1705 int ret;
1706
1707 if ((pa <= pb) && (pa <= pc)) {
1708 ret = a;
1709 } else if (pb <= pc) {
1710 ret = b;
1711 } else {
1712 ret = c;
1713 }
1714 curr[i] += (byte)(ret);
1715 }
1716 break;
1717 default:
1718 // Error -- unknown filter type
1719 throw new RuntimeException("PNG filter unknown.");
1720 }
1721 try {
1722 fout.write(curr);
1723 }
1724 catch (IOException ioe) {
1725 // Never happens
1726 }
1727
1728 // Swap curr and prior
1729 byte[] tmp = prior;
1730 prior = curr;
1731 curr = tmp;
1732 }
1733 }
1734
1735 /** A helper to FlateDecode.
1736 * @param in the input data
1737 * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE>
1738 * to try to read a corrupted stream
1739 * @return the decoded data
1740 */
1741 public static byte[] FlateDecode(byte in[], boolean strict) {
1742 ByteArrayInputStream stream = new ByteArrayInputStream(in);
1743 InflaterInputStream zip = new InflaterInputStream(stream);
1744 ByteArrayOutputStream out = new ByteArrayOutputStream();
1745 byte b[] = new byte[strict ? 4092 : 1];
1746 try {
1747 int n;
1748 while ((n = zip.read(b)) >= 0) {
1749 out.write(b, 0, n);
1750 }
1751 zip.close();
1752 out.close();
1753 return out.toByteArray();
1754 }
1755 catch (Exception e) {
1756 if (strict)
1757 return null;
1758 return out.toByteArray();
1759 }
1760 }
1761
1762 /** Decodes a stream that has the ASCIIHexDecode filter.
1763 * @param in the input data
1764 * @return the decoded data
1765 */
1766 public static byte[] ASCIIHexDecode(byte in[]) {
1767 ByteArrayOutputStream out = new ByteArrayOutputStream();
1768 boolean first = true;
1769 int n1 = 0;
1770 for (int k = 0; k < in.length; ++k) {
1771 int ch = in[k] & 0xff;
1772 if (ch == '>')
1773 break;
1774 if (PRTokeniser.isWhitespace(ch))
1775 continue;
1776 int n = PRTokeniser.getHex(ch);
1777 if (n == -1)
1778 throw new RuntimeException("Illegal character in ASCIIHexDecode.");
1779 if (first)
1780 n1 = n;
1781 else
1782 out.write((byte)((n1 << 4) + n));
1783 first = !first;
1784 }
1785 if (!first)
1786 out.write((byte)(n1 << 4));
1787 return out.toByteArray();
1788 }
1789
1790 /** Decodes a stream that has the ASCII85Decode filter.
1791 * @param in the input data
1792 * @return the decoded data
1793 */
1794 public static byte[] ASCII85Decode(byte in[]) {
1795 ByteArrayOutputStream out = new ByteArrayOutputStream();
1796 int state = 0;
1797 int chn[] = new int[5];
1798 for (int k = 0; k < in.length; ++k) {
1799 int ch = in[k] & 0xff;
1800 if (ch == '~')
1801 break;
1802 if (PRTokeniser.isWhitespace(ch))
1803 continue;
1804 if (ch == 'z' && state == 0) {
1805 out.write(0);
1806 out.write(0);
1807 out.write(0);
1808 out.write(0);
1809 continue;
1810 }
1811 if (ch < '!' || ch > 'u')
1812 throw new RuntimeException("Illegal character in ASCII85Decode.");
1813 chn[state] = ch - '!';
1814 ++state;
1815 if (state == 5) {
1816 state = 0;
1817 int r = 0;
1818 for (int j = 0; j < 5; ++j)
1819 r = r * 85 + chn[j];
1820 out.write((byte)(r >> 24));
1821 out.write((byte)(r >> 16));
1822 out.write((byte)(r >> 8));
1823 out.write((byte)r);
1824 }
1825 }
1826 int r = 0;
1827 // We'll ignore the next two lines for the sake of perpetuating broken PDFs
1828 // if (state == 1)
1829 // throw new RuntimeException("Illegal length in ASCII85Decode.");
1830 if (state == 2) {
1831 r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + 85 * 85 * 85 + 85 * 85 + 85;
1832 out.write((byte)(r >> 24));
1833 }
1834 else if (state == 3) {
1835 r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + 85 * 85 + 85;
1836 out.write((byte)(r >> 24));
1837 out.write((byte)(r >> 16));
1838 }
1839 else if (state == 4) {
1840 r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + chn[3] * 85 + 85;
1841 out.write((byte)(r >> 24));
1842 out.write((byte)(r >> 16));
1843 out.write((byte)(r >> 8));
1844 }
1845 return out.toByteArray();
1846 }
1847
1848 /** Decodes a stream that has the LZWDecode filter.
1849 * @param in the input data
1850 * @return the decoded data
1851 */
1852 public static byte[] LZWDecode(byte in[]) {
1853 ByteArrayOutputStream out = new ByteArrayOutputStream();
1854 LZWDecoder lzw = new LZWDecoder();
1855 lzw.decode(in, out);
1856 return out.toByteArray();
1857 }
1858
1859 /** Checks if the document had errors and was rebuilt.
1860 * @return true if rebuilt.
1861 *
1862 */
1863 public boolean isRebuilt() {
1864 return this.rebuilt;
1865 }
1866
1867 /** Gets the dictionary that represents a page.
1868 * @param pageNum the page number. 1 is the first
1869 * @return the page dictionary
1870 */
1871 public PdfDictionary getPageN(int pageNum) {
1872 PdfDictionary dic = pageRefs.getPageN(pageNum);
1873 if (dic == null)
1874 return null;
1875 if (appendable)
1876 dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
1877 return dic;
1878 }
1879
1880 /**
1881 * @param pageNum
1882 * @return a Dictionary object
1883 */
1884 public PdfDictionary getPageNRelease(int pageNum) {
1885 PdfDictionary dic = getPageN(pageNum);
1886 pageRefs.releasePage(pageNum);
1887 return dic;
1888 }
1889
1890 /**
1891 * @param pageNum
1892 */
1893 public void releasePage(int pageNum) {
1894 pageRefs.releasePage(pageNum);
1895 }
1896
1897 /**
1898 *
1899 */
1900 public void resetReleasePage() {
1901 pageRefs.resetReleasePage();
1902 }
1903
1904 /** Gets the page reference to this page.
1905 * @param pageNum the page number. 1 is the first
1906 * @return the page reference
1907 */
1908 public PRIndirectReference getPageOrigRef(int pageNum) {
1909 return pageRefs.getPageOrigRef(pageNum);
1910 }
1911
1912 /** Gets the contents of the page.
1913 * @param pageNum the page number. 1 is the first
1914 * @param file the location of the PDF document
1915 * @throws IOException on error
1916 * @return the content
1917 */
1918 public byte[] getPageContent(int pageNum, RandomAccessFileOrArray file) throws IOException{
1919 PdfDictionary page = getPageNRelease(pageNum);
1920 if (page == null)
1921 return null;
1922 PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS));
1923 if (contents == null)
1924 return new byte[0];
1925 ByteArrayOutputStream bout = null;
1926 if (contents.isStream()) {
1927 return getStreamBytes((PRStream)contents, file);
1928 }
1929 else if (contents.isArray()) {
1930 PdfArray array = (PdfArray)contents;
1931 ArrayList list = array.getArrayList();
1932 bout = new ByteArrayOutputStream();
1933 for (int k = 0; k < list.size(); ++k) {
1934 PdfObject item = getPdfObjectRelease((PdfObject)list.get(k));
1935 if (item == null || !item.isStream())
1936 continue;
1937 byte[] b = getStreamBytes((PRStream)item, file);
1938 bout.write(b);
1939 if (k != list.size() - 1)
1940 bout.write('\n');
1941 }
1942 return bout.toByteArray();
1943 }
1944 else
1945 return new byte[0];
1946 }
1947
1948 /** Gets the contents of the page.
1949 * @param pageNum the page number. 1 is the first
1950 * @throws IOException on error
1951 * @return the content
1952 */
1953 public byte[] getPageContent(int pageNum) throws IOException{
1954 RandomAccessFileOrArray rf = getSafeFile();
1955 try {
1956 rf.reOpen();
1957 return getPageContent(pageNum, rf);
1958 }
1959 finally {
1960 try{rf.close();}catch(Exception e){}
1961 }
1962 }
1963
1964 protected void killXref(PdfObject obj) {
1965 if (obj == null)
1966 return;
1967 if ((obj instanceof PdfIndirectReference) && !obj.isIndirect())
1968 return;
1969 switch (obj.type()) {
1970 case PdfObject.INDIRECT: {
1971 int xr = ((PRIndirectReference)obj).getNumber();
1972 obj = (PdfObject)xrefObj.get(xr);
1973 xrefObj.set(xr, null);
1974 freeXref = xr;
1975 killXref(obj);
1976 break;
1977 }
1978 case PdfObject.ARRAY: {
1979 ArrayList t = ((PdfArray)obj).getArrayList();
1980 for (int i = 0; i < t.size(); ++i)
1981 killXref((PdfObject)t.get(i));
1982 break;
1983 }
1984 case PdfObject.STREAM:
1985 case PdfObject.DICTIONARY: {
1986 PdfDictionary dic = (PdfDictionary)obj;
1987 for (Iterator i = dic.getKeys().iterator(); i.hasNext();){
1988 killXref(dic.get((PdfName)i.next()));
1989 }
1990 break;
1991 }
1992 }
1993 }
1994
1995 /** Sets the contents of the page.
1996 * @param content the new page content
1997 * @param pageNum the page number. 1 is the first
1998 */
1999 public void setPageContent(int pageNum, byte content[]) {
2000 setPageContent(pageNum, content, PdfStream.DEFAULT_COMPRESSION);
2001 }
2002 /** Sets the contents of the page.
2003 * @param content the new page content
2004 * @param pageNum the page number. 1 is the first
2005 * @since 2.1.3 (the method already existed without param compressionLevel)
2006 */
2007 public void setPageContent(int pageNum, byte content[], int compressionLevel) {
2008 PdfDictionary page = getPageN(pageNum);
2009 if (page == null)
2010 return;
2011 PdfObject contents = page.get(PdfName.CONTENTS);
2012 freeXref = -1;
2013 killXref(contents);
2014 if (freeXref == -1) {
2015 xrefObj.add(null);
2016 freeXref = xrefObj.size() - 1;
2017 }
2018 page.put(PdfName.CONTENTS, new PRIndirectReference(this, freeXref));
2019 xrefObj.set(freeXref, new PRStream(this, content, compressionLevel));
2020 }
2021
2022 /** Get the content from a stream applying the required filters.
2023 * @param stream the stream
2024 * @param file the location where the stream is
2025 * @throws IOException on error
2026 * @return the stream content
2027 */
2028 public static byte[] getStreamBytes(PRStream stream, RandomAccessFileOrArray file) throws IOException {
2029 PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
2030 byte[] b = getStreamBytesRaw(stream, file);
2031 ArrayList filters = new ArrayList();
2032 if (filter != null) {
2033 if (filter.isName())
2034 filters.add(filter);
2035 else if (filter.isArray())
2036 filters = ((PdfArray)filter).getArrayList();
2037 }
2038 ArrayList dp = new ArrayList();
2039 PdfObject dpo = getPdfObjectRelease(stream.get(PdfName.DECODEPARMS));
2040 if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
2041 dpo = getPdfObjectRelease(stream.get(PdfName.DP));
2042 if (dpo != null) {
2043 if (dpo.isDictionary())
2044 dp.add(dpo);
2045 else if (dpo.isArray())
2046 dp = ((PdfArray)dpo).getArrayList();
2047 }
2048 String name;
2049 for (int j = 0; j < filters.size(); ++j) {
2050 name = ((PdfName)getPdfObjectRelease((PdfObject)filters.get(j))).toString();
2051 if (name.equals("/FlateDecode") || name.equals("/Fl")) {
2052 b = FlateDecode(b);
2053 PdfObject dicParam = null;
2054 if (j < dp.size()) {
2055 dicParam = (PdfObject)dp.get(j);
2056 b = decodePredictor(b, dicParam);
2057 }
2058 }
2059 else if (name.equals("/ASCIIHexDecode") || name.equals("/AHx"))
2060 b = ASCIIHexDecode(b);
2061 else if (name.equals("/ASCII85Decode") || name.equals("/A85"))
2062 b = ASCII85Decode(b);
2063 else if (name.equals("/LZWDecode")) {
2064 b = LZWDecode(b);
2065 PdfObject dicParam = null;
2066 if (j < dp.size()) {
2067 dicParam = (PdfObject)dp.get(j);
2068 b = decodePredictor(b, dicParam);
2069 }
2070 }
2071 else if (name.equals("/Crypt")) {
2072 }
2073 else
2074 throw new IOException("The filter " + name + " is not supported.");
2075 }
2076 return b;
2077 }
2078
2079 /** Get the content from a stream applying the required filters.
2080 * @param stream the stream
2081 * @throws IOException on error
2082 * @return the stream content
2083 */
2084 public static byte[] getStreamBytes(PRStream stream) throws IOException {
2085 RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2086 try {
2087 rf.reOpen();
2088 return getStreamBytes(stream, rf);
2089 }
2090 finally {
2091 try{rf.close();}catch(Exception e){}
2092 }
2093 }
2094
2095 /** Get the content from a stream as it is without applying any filter.
2096 * @param stream the stream
2097 * @param file the location where the stream is
2098 * @throws IOException on error
2099 * @return the stream content
2100 */
2101 public static byte[] getStreamBytesRaw(PRStream stream, RandomAccessFileOrArray file) throws IOException {
2102 PdfReader reader = stream.getReader();
2103 byte b[];
2104 if (stream.getOffset() < 0)
2105 b = stream.getBytes();
2106 else {
2107 b = new byte[stream.getLength()];
2108 file.seek(stream.getOffset());
2109 file.readFully(b);
2110 PdfEncryption decrypt = reader.getDecrypt();
2111 if (decrypt != null) {
2112 PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
2113 ArrayList filters = new ArrayList();
2114 if (filter != null) {
2115 if (filter.isName())
2116 filters.add(filter);
2117 else if (filter.isArray())
2118 filters = ((PdfArray)filter).getArrayList();
2119 }
2120 boolean skip = false;
2121 for (int k = 0; k < filters.size(); ++k) {
2122 PdfObject obj = getPdfObjectRelease((PdfObject)filters.get(k));
2123 if (obj != null && obj.toString().equals("/Crypt")) {
2124 skip = true;
2125 break;
2126 }
2127 }
2128 if (!skip) {
2129 decrypt.setHashKey(stream.getObjNum(), stream.getObjGen());
2130 b = decrypt.decryptByteArray(b);
2131 }
2132 }
2133 }
2134 return b;
2135 }
2136
2137 /** Get the content from a stream as it is without applying any filter.
2138 * @param stream the stream
2139 * @throws IOException on error
2140 * @return the stream content
2141 */
2142 public static byte[] getStreamBytesRaw(PRStream stream) throws IOException {
2143 RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2144 try {
2145 rf.reOpen();
2146 return getStreamBytesRaw(stream, rf);
2147 }
2148 finally {
2149 try{rf.close();}catch(Exception e){}
2150 }
2151 }
2152
2153 /** Eliminates shared streams if they exist. */
2154 public void eliminateSharedStreams() {
2155 if (!sharedStreams)
2156 return;
2157 sharedStreams = false;
2158 if (pageRefs.size() == 1)
2159 return;
2160 ArrayList newRefs = new ArrayList();
2161 ArrayList newStreams = new ArrayList();
2162 IntHashtable visited = new IntHashtable();
2163 for (int k = 1; k <= pageRefs.size(); ++k) {
2164 PdfDictionary page = pageRefs.getPageN(k);
2165 if (page == null)
2166 continue;
2167 PdfObject contents = getPdfObject(page.get(PdfName.CONTENTS));
2168 if (contents == null)
2169 continue;
2170 if (contents.isStream()) {
2171 PRIndirectReference ref = (PRIndirectReference)page.get(PdfName.CONTENTS);
2172 if (visited.containsKey(ref.getNumber())) {
2173 // need to duplicate
2174 newRefs.add(ref);
2175 newStreams.add(new PRStream((PRStream)contents, null));
2176 }
2177 else
2178 visited.put(ref.getNumber(), 1);
2179 }
2180 else if (contents.isArray()) {
2181 PdfArray array = (PdfArray)contents;
2182 ArrayList list = array.getArrayList();
2183 for (int j = 0; j < list.size(); ++j) {
2184 PRIndirectReference ref = (PRIndirectReference)list.get(j);
2185 if (visited.containsKey(ref.getNumber())) {
2186 // need to duplicate
2187 newRefs.add(ref);
2188 newStreams.add(new PRStream((PRStream)getPdfObject(ref), null));
2189 }
2190 else
2191 visited.put(ref.getNumber(), 1);
2192 }
2193 }
2194 }
2195 if (newStreams.isEmpty())
2196 return;
2197 for (int k = 0; k < newStreams.size(); ++k) {
2198 xrefObj.add(newStreams.get(k));
2199 PRIndirectReference ref = (PRIndirectReference)newRefs.get(k);
2200 ref.setNumber(xrefObj.size() - 1, 0);
2201 }
2202 }
2203
2204 /** Checks if the document was changed.
2205 * @return <CODE>true</CODE> if the document was changed,
2206 * <CODE>false</CODE> otherwise
2207 */
2208 public boolean isTampered() {
2209 return tampered;
2210 }
2211
2212 /**
2213 * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper.
2214 * @param tampered the tampered state
2215 */
2216 public void setTampered(boolean tampered) {
2217 this.tampered = tampered;
2218 pageRefs.keepPages();
2219 }
2220
2221 /** Gets the XML metadata.
2222 * @throws IOException on error
2223 * @return the XML metadata
2224 */
2225 public byte[] getMetadata() throws IOException {
2226 PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA));
2227 if (!(obj instanceof PRStream))
2228 return null;
2229 RandomAccessFileOrArray rf = getSafeFile();
2230 byte b[] = null;
2231 try {
2232 rf.reOpen();
2233 b = getStreamBytes((PRStream)obj, rf);
2234 }
2235 finally {
2236 try {
2237 rf.close();
2238 }
2239 catch (Exception e) {
2240 // empty on purpose
2241 }
2242 }
2243 return b;
2244 }
2245
2246 /**
2247 * Gets the byte address of the last xref table.
2248 * @return the byte address of the last xref table
2249 */
2250 public int getLastXref() {
2251 return lastXref;
2252 }
2253
2254 /**
2255 * Gets the number of xref objects.
2256 * @return the number of xref objects
2257 */
2258 public int getXrefSize() {
2259 return xrefObj.size();
2260 }
2261
2262 /**
2263 * Gets the byte address of the %%EOF marker.
2264 * @return the byte address of the %%EOF marker
2265 */
2266 public int getEofPos() {
2267 return eofPos;
2268 }
2269
2270 /**
2271 * Gets the PDF version. Only the last version char is returned. For example
2272 * version 1.4 is returned as '4'.
2273 * @return the PDF version
2274 */
2275 public char getPdfVersion() {
2276 return pdfVersion;
2277 }
2278
2279 /**
2280 * Returns <CODE>true</CODE> if the PDF is encrypted.
2281 * @return <CODE>true</CODE> if the PDF is encrypted
2282 */
2283 public boolean isEncrypted() {
2284 return encrypted;
2285 }
2286
2287 /**
2288 * Gets the encryption permissions. It can be used directly in
2289 * <CODE>PdfWriter.setEncryption()</CODE>.
2290 * @return the encryption permissions
2291 */
2292 public int getPermissions() {
2293 return pValue;
2294 }
2295
2296 /**
2297 * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption.
2298 * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption
2299 */
2300 public boolean is128Key() {
2301 return rValue == 3;
2302 }
2303
2304 /**
2305 * Gets the trailer dictionary
2306 * @return the trailer dictionary
2307 */
2308 public PdfDictionary getTrailer() {
2309 return trailer;
2310 }
2311
2312 PdfEncryption getDecrypt() {
2313 return decrypt;
2314 }
2315
2316 static boolean equalsn(byte a1[], byte a2[]) {
2317 int length = a2.length;
2318 for (int k = 0; k < length; ++k) {
2319 if (a1[k] != a2[k])
2320 return false;
2321 }
2322 return true;
2323 }
2324
2325 static boolean existsName(PdfDictionary dic, PdfName key, PdfName value) {
2326 PdfObject type = getPdfObjectRelease(dic.get(key));
2327 if (type == null || !type.isName())
2328 return false;
2329 PdfName name = (PdfName)type;
2330 return name.equals(value);
2331 }
2332
2333 static String getFontName(PdfDictionary dic) {
2334 if (dic == null)
2335 return null;
2336 PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT));
2337 if (type == null || !type.isName())
2338 return null;
2339 return PdfName.decodeName(type.toString());
2340 }
2341
2342 static String getSubsetPrefix(PdfDictionary dic) {
2343 if (dic == null)
2344 return null;
2345 String s = getFontName(dic);
2346 if (s == null)
2347 return null;
2348 if (s.length() < 8 || s.charAt(6) != '+')
2349 return null;
2350 for (int k = 0; k < 6; ++k) {
2351 char c = s.charAt(k);
2352 if (c < 'A' || c > 'Z')
2353 return null;
2354 }
2355 return s;
2356 }
2357
2358 /** Finds all the font subsets and changes the prefixes to some
2359 * random values.
2360 * @return the number of font subsets altered
2361 */
2362 public int shuffleSubsetNames() {
2363 int total = 0;
2364 for (int k = 1; k < xrefObj.size(); ++k) {
2365 PdfObject obj = getPdfObjectRelease(k);
2366 if (obj == null || !obj.isDictionary())
2367 continue;
2368 PdfDictionary dic = (PdfDictionary)obj;
2369 if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
2370 continue;
2371 if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
2372 || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
2373 || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
2374 String s = getSubsetPrefix(dic);
2375 if (s == null)
2376 continue;
2377 String ns = BaseFont.createSubsetPrefix() + s.substring(7);
2378 PdfName newName = new PdfName(ns);
2379 dic.put(PdfName.BASEFONT, newName);
2380 setXrefPartialObject(k, dic);
2381 ++total;
2382 PdfDictionary fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
2383 if (fd == null)
2384 continue;
2385 fd.put(PdfName.FONTNAME, newName);
2386 }
2387 else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) {
2388 String s = getSubsetPrefix(dic);
2389 PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.DESCENDANTFONTS));
2390 if (arr == null)
2391 continue;
2392 ArrayList list = arr.getArrayList();
2393 if (list.isEmpty())
2394 continue;
2395 PdfDictionary desc = (PdfDictionary)getPdfObject((PdfObject)list.get(0));
2396 String sde = getSubsetPrefix(desc);
2397 if (sde == null)
2398 continue;
2399 String ns = BaseFont.createSubsetPrefix();
2400 if (s != null)
2401 dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7)));
2402 setXrefPartialObject(k, dic);
2403 PdfName newName = new PdfName(ns + sde.substring(7));
2404 desc.put(PdfName.BASEFONT, newName);
2405 ++total;
2406 PdfDictionary fd = (PdfDictionary)getPdfObject(desc.get(PdfName.FONTDESCRIPTOR));
2407 if (fd == null)
2408 continue;
2409 fd.put(PdfName.FONTNAME, newName);
2410 }
2411 }
2412 return total;
2413 }
2414
2415 /** Finds all the fonts not subset but embedded and marks them as subset.
2416 * @return the number of fonts altered
2417 */
2418 public int createFakeFontSubsets() {
2419 int total = 0;
2420 for (int k = 1; k < xrefObj.size(); ++k) {
2421 PdfObject obj = getPdfObjectRelease(k);
2422 if (obj == null || !obj.isDictionary())
2423 continue;
2424 PdfDictionary dic = (PdfDictionary)obj;
2425 if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
2426 continue;
2427 if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
2428 || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
2429 || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
2430 String s = getSubsetPrefix(dic);
2431 if (s != null)
2432 continue;
2433 s = getFontName(dic);
2434 if (s == null)
2435 continue;
2436 String ns = BaseFont.createSubsetPrefix() + s;
2437 PdfDictionary fd = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.FONTDESCRIPTOR));
2438 if (fd == null)
2439 continue;
2440 if (fd.get(PdfName.FONTFILE) == null && fd.get(PdfName.FONTFILE2) == null
2441 && fd.get(PdfName.FONTFILE3) == null)
2442 continue;
2443 fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
2444 PdfName newName = new PdfName(ns);
2445 dic.put(PdfName.BASEFONT, newName);
2446 fd.put(PdfName.FONTNAME, newName);
2447 setXrefPartialObject(k, dic);
2448 ++total;
2449 }
2450 }
2451 return total;
2452 }
2453
2454 private static PdfArray getNameArray(PdfObject obj) {
2455 if (obj == null)
2456 return null;
2457 obj = getPdfObjectRelease(obj);
2458 if (obj == null)
2459 return null;
2460 if (obj.isArray())
2461 return (PdfArray)obj;
2462 else if (obj.isDictionary()) {
2463 PdfObject arr2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.D));
2464 if (arr2 != null && arr2.isArray())
2465 return (PdfArray)arr2;
2466 }
2467 return null;
2468 }
2469
2470 /**
2471 * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name
2472 * and the value is the destinations array.
2473 * @return gets all the named destinations
2474 */
2475 public HashMap getNamedDestination() {
2476 HashMap names = getNamedDestinationFromNames();
2477 names.putAll(getNamedDestinationFromStrings());
2478 return names;
2479 }
2480
2481 /**
2482 * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2483 * and the value is the destinations array.
2484 * @return gets the named destinations
2485 */
2486 public HashMap getNamedDestinationFromNames() {
2487 HashMap names = new HashMap();
2488 if (catalog.get(PdfName.DESTS) != null) {
2489 PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.DESTS));
2490 if (dic == null)
2491 return names;
2492 Set keys = dic.getKeys();
2493 for (Iterator it = keys.iterator(); it.hasNext();) {
2494 PdfName key = (PdfName)it.next();
2495 String name = PdfName.decodeName(key.toString());
2496 PdfArray arr = getNameArray(dic.get(key));
2497 if (arr != null)
2498 names.put(name, arr);
2499 }
2500 }
2501 return names;
2502 }
2503
2504 /**
2505 * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2506 * and the value is the destinations array.
2507 * @return gets the named destinations
2508 */
2509 public HashMap getNamedDestinationFromStrings() {
2510 if (catalog.get(PdfName.NAMES) != null) {
2511 PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
2512 if (dic != null) {
2513 dic = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.DESTS));
2514 if (dic != null) {
2515 HashMap names = PdfNameTree.readTree(dic);
2516 for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
2517 Map.Entry entry = (Map.Entry)it.next();
2518 PdfArray arr = getNameArray((PdfObject)entry.getValue());
2519 if (arr != null)
2520 entry.setValue(arr);
2521 else
2522 it.remove();
2523 }
2524 return names;
2525 }
2526 }
2527 }
2528 return new HashMap();
2529 }
2530
2531 private boolean replaceNamedDestination(PdfObject obj, HashMap names) {
2532 obj = getPdfObject(obj);
2533 int objIdx = lastXrefPartial;
2534 releaseLastXrefPartial();
2535 if (obj != null && obj.isDictionary()) {
2536 PdfObject ob2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.DEST));
2537 String name = null;
2538 if (ob2 != null) {
2539 if (ob2.isName())
2540 name = PdfName.decodeName(ob2.toString());
2541 else if (ob2.isString())
2542 name = ob2.toString();
2543 PdfArray dest = (PdfArray)names.get(name);
2544 if (dest != null) {
2545 ((PdfDictionary)obj).put(PdfName.DEST, dest);
2546 setXrefPartialObject(objIdx, obj);
2547 return true;
2548 }
2549 }
2550 else if ((ob2 = getPdfObject(((PdfDictionary)obj).get(PdfName.A))) != null) {
2551 int obj2Idx = lastXrefPartial;
2552 releaseLastXrefPartial();
2553 PdfDictionary dic = (PdfDictionary)ob2;
2554 PdfName type = (PdfName)getPdfObjectRelease(dic.get(PdfName.S));
2555 if (PdfName.GOTO.equals(type)) {
2556 PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D));
2557 if (ob3 != null) {
2558 if (ob3.isName())
2559 name = PdfName.decodeName(ob3.toString());
2560 else if (ob3.isString())
2561 name = ob3.toString();
2562 }
2563 PdfArray dest = (PdfArray)names.get(name);
2564 if (dest != null) {
2565 dic.put(PdfName.D, dest);
2566 setXrefPartialObject(obj2Idx, ob2);
2567 setXrefPartialObject(objIdx, obj);
2568 return true;
2569 }
2570 }
2571 }
2572 }
2573 return false;
2574 }
2575
2576 /**
2577 * Removes all the fields from the document.
2578 */
2579 public void removeFields() {
2580 pageRefs.resetReleasePage();
2581 for (int k = 1; k <= pageRefs.size(); ++k) {
2582 PdfDictionary page = pageRefs.getPageN(k);
2583 PdfArray annots = (PdfArray)getPdfObject(page.get(PdfName.ANNOTS));
2584 if (annots == null) {
2585 pageRefs.releasePage(k);
2586 continue;
2587 }
2588 ArrayList arr = annots.getArrayList();
2589 for (int j = 0; j < arr.size(); ++j) {
2590 PdfObject obj = getPdfObjectRelease((PdfObject)arr.get(j));
2591 if (obj == null || !obj.isDictionary())
2592 continue;
2593 PdfDictionary annot = (PdfDictionary)obj;
2594 if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE)))
2595 arr.remove(j--);
2596 }
2597 if (arr.isEmpty())
2598 page.remove(PdfName.ANNOTS);
2599 else
2600 pageRefs.releasePage(k);
2601 }
2602 catalog.remove(PdfName.ACROFORM);
2603 pageRefs.resetReleasePage();
2604 }
2605
2606 /**
2607 * Removes all the annotations and fields from the document.
2608 */
2609 public void removeAnnotations() {
2610 pageRefs.resetReleasePage();
2611 for (int k = 1; k <= pageRefs.size(); ++k) {
2612 PdfDictionary page = pageRefs.getPageN(k);
2613 if (page.get(PdfName.ANNOTS) == null)
2614 pageRefs.releasePage(k);
2615 else
2616 page.remove(PdfName.ANNOTS);
2617 }
2618 catalog.remove(PdfName.ACROFORM);
2619 pageRefs.resetReleasePage();
2620 }
2621
2622 public ArrayList getLinks(int page) {
2623 pageRefs.resetReleasePage();
2624 ArrayList result = new ArrayList();
2625 PdfDictionary pageDic = pageRefs.getPageN(page);
2626 if (pageDic.get(PdfName.ANNOTS) != null) {
2627 PdfArray annots = (PdfArray)getPdfObject(pageDic.get(PdfName.ANNOTS));
2628 ArrayList arr = annots.getArrayList();
2629 for (int j = 0; j < arr.size(); ++j) {
2630 PdfDictionary annot = (PdfDictionary)getPdfObjectRelease((PdfObject)arr.get(j));
2631
2632 if (PdfName.LINK.equals(annot.get(PdfName.SUBTYPE))) {
2633 result.add(new PdfAnnotation.PdfImportedLink(annot));
2634 }
2635 }
2636 }
2637 pageRefs.releasePage(page);
2638 pageRefs.resetReleasePage();
2639 return result;
2640 }
2641
2642 private void iterateBookmarks(PdfObject outlineRef, HashMap names) {
2643 while (outlineRef != null) {
2644 replaceNamedDestination(outlineRef, names);
2645 PdfDictionary outline = (PdfDictionary)getPdfObjectRelease(outlineRef);
2646 PdfObject first = outline.get(PdfName.FIRST);
2647 if (first != null) {
2648 iterateBookmarks(first, names);
2649 }
2650 outlineRef = outline.get(PdfName.NEXT);
2651 }
2652 }
2653
2654 /** Replaces all the local named links with the actual destinations. */
2655 public void consolidateNamedDestinations() {
2656 if (consolidateNamedDestinations)
2657 return;
2658 consolidateNamedDestinations = true;
2659 HashMap names = getNamedDestination();
2660 if (names.isEmpty())
2661 return;
2662 for (int k = 1; k <= pageRefs.size(); ++k) {
2663 PdfDictionary page = pageRefs.getPageN(k);
2664 PdfObject annotsRef;
2665 PdfArray annots = (PdfArray)getPdfObject(annotsRef = page.get(PdfName.ANNOTS));
2666 int annotIdx = lastXrefPartial;
2667 releaseLastXrefPartial();
2668 if (annots == null) {
2669 pageRefs.releasePage(k);
2670 continue;
2671 }
2672 ArrayList list = annots.getArrayList();
2673 boolean commitAnnots = false;
2674 for (int an = 0; an < list.size(); ++an) {
2675 PdfObject objRef = (PdfObject)list.get(an);
2676 if (replaceNamedDestination(objRef, names) && !objRef.isIndirect())
2677 commitAnnots = true;
2678 }
2679 if (commitAnnots)
2680 setXrefPartialObject(annotIdx, annots);
2681 if (!commitAnnots || annotsRef.isIndirect())
2682 pageRefs.releasePage(k);
2683 }
2684 PdfDictionary outlines = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.OUTLINES));
2685 if (outlines == null)
2686 return;
2687 iterateBookmarks(outlines.get(PdfName.FIRST), names);
2688 }
2689
2690 protected static PdfDictionary duplicatePdfDictionary(PdfDictionary original, PdfDictionary copy, PdfReader newReader) {
2691 if (copy == null)
2692 copy = new PdfDictionary();
2693 for (Iterator it = original.getKeys().iterator(); it.hasNext();) {
2694 PdfName key = (PdfName)it.next();
2695 copy.put(key, duplicatePdfObject(original.get(key), newReader));
2696 }
2697 return copy;
2698 }
2699
2700 protected static PdfObject duplicatePdfObject(PdfObject original, PdfReader newReader) {
2701 if (original == null)
2702 return null;
2703 switch (original.type()) {
2704 case PdfObject.DICTIONARY: {
2705 return duplicatePdfDictionary((PdfDictionary)original, null, newReader);
2706 }
2707 case PdfObject.STREAM: {
2708 PRStream org = (PRStream)original;
2709 PRStream stream = new PRStream(org, null, newReader);
2710 duplicatePdfDictionary(org, stream, newReader);
2711 return stream;
2712 }
2713 case PdfObject.ARRAY: {
2714 ArrayList list = ((PdfArray)original).getArrayList();
2715 PdfArray arr = new PdfArray();
2716 for (Iterator it = list.iterator(); it.hasNext();) {
2717 arr.add(duplicatePdfObject((PdfObject)it.next(), newReader));
2718 }
2719 return arr;
2720 }
2721 case PdfObject.INDIRECT: {
2722 PRIndirectReference org = (PRIndirectReference)original;
2723 return new PRIndirectReference(newReader, org.getNumber(), org.getGeneration());
2724 }
2725 default:
2726 return original;
2727 }
2728 }
2729
2730 /**
2731 * Closes the reader
2732 */
2733 public void close() {
2734 if (!partial)
2735 return;
2736 try {
2737 tokens.close();
2738 }
2739 catch (IOException e) {
2740 throw new ExceptionConverter(e);
2741 }
2742 }
2743
2744 protected void removeUnusedNode(PdfObject obj, boolean hits[]) {
2745 Stack state = new Stack();
2746 state.push(obj);
2747 while (!state.empty()) {
2748 Object current = state.pop();
2749 if (current == null)
2750 continue;
2751 ArrayList ar = null;
2752 PdfDictionary dic = null;
2753 PdfName[] keys = null;
2754 Object[] objs = null;
2755 int idx = 0;
2756 if (current instanceof PdfObject) {
2757 obj = (PdfObject)current;
2758 switch (obj.type()) {
2759 case PdfObject.DICTIONARY:
2760 case PdfObject.STREAM:
2761 dic = (PdfDictionary)obj;
2762 keys = new PdfName[dic.size()];
2763 dic.getKeys().toArray(keys);
2764 break;
2765 case PdfObject.ARRAY:
2766 ar = ((PdfArray)obj).getArrayList();
2767 break;
2768 case PdfObject.INDIRECT:
2769 PRIndirectReference ref = (PRIndirectReference)obj;
2770 int num = ref.getNumber();
2771 if (!hits[num]) {
2772 hits[num] = true;
2773 state.push(getPdfObjectRelease(ref));
2774 }
2775 continue;
2776 default:
2777 continue;
2778 }
2779 }
2780 else {
2781 objs = (Object[])current;
2782 if (objs[0] instanceof ArrayList) {
2783 ar = (ArrayList)objs[0];
2784 idx = ((Integer)objs[1]).intValue();
2785 }
2786 else {
2787 keys = (PdfName[])objs[0];
2788 dic = (PdfDictionary)objs[1];
2789 idx = ((Integer)objs[2]).intValue();
2790 }
2791 }
2792 if (ar != null) {
2793 for (int k = idx; k < ar.size(); ++k) {
2794 PdfObject v = (PdfObject)ar.get(k);
2795 if (v.isIndirect()) {
2796 int num = ((PRIndirectReference)v).getNumber();
2797 if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
2798 ar.set(k, PdfNull.PDFNULL);
2799 continue;
2800 }
2801 }
2802 if (objs == null)
2803 state.push(new Object[]{ar, new Integer(k + 1)});
2804 else {
2805 objs[1] = new Integer(k + 1);
2806 state.push(objs);
2807 }
2808 state.push(v);
2809 break;
2810 }
2811 }
2812 else {
2813 for (int k = idx; k < keys.length; ++k) {
2814 PdfName key = keys[k];
2815 PdfObject v = dic.get(key);
2816 if (v.isIndirect()) {
2817 int num = ((PRIndirectReference)v).getNumber();
2818 if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
2819 dic.put(key, PdfNull.PDFNULL);
2820 continue;
2821 }
2822 }
2823 if (objs == null)
2824 state.push(new Object[]{keys, dic, new Integer(k + 1)});
2825 else {
2826 objs[2] = new Integer(k + 1);
2827 state.push(objs);
2828 }
2829 state.push(v);
2830 break;
2831 }
2832 }
2833 }
2834 }
2835
2836 /** Removes all the unreachable objects.
2837 * @return the number of indirect objects removed
2838 */
2839 public int removeUnusedObjects() {
2840 boolean hits[] = new boolean[xrefObj.size()];
2841 removeUnusedNode(trailer, hits);
2842 int total = 0;
2843 if (partial) {
2844 for (int k = 1; k < hits.length; ++k) {
2845 if (!hits[k]) {
2846 xref[k * 2] = -1;
2847 xref[k * 2 + 1] = 0;
2848 xrefObj.set(k, null);
2849 ++total;
2850 }
2851 }
2852 }
2853 else {
2854 for (int k = 1; k < hits.length; ++k) {
2855 if (!hits[k]) {
2856 xrefObj.set(k, null);
2857 ++total;
2858 }
2859 }
2860 }
2861 return total;
2862 }
2863
2864 /** Gets a read-only version of <CODE>AcroFields</CODE>.
2865 * @return a read-only version of <CODE>AcroFields</CODE>
2866 */
2867 public AcroFields getAcroFields() {
2868 return new AcroFields(this, null);
2869 }
2870
2871 /**
2872 * Gets the global document JavaScript.
2873 * @param file the document file
2874 * @throws IOException on error
2875 * @return the global document JavaScript
2876 */
2877 public String getJavaScript(RandomAccessFileOrArray file) throws IOException {
2878 PdfDictionary names = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
2879 if (names == null)
2880 return null;
2881 PdfDictionary js = (PdfDictionary)getPdfObjectRelease(names.get(PdfName.JAVASCRIPT));
2882 if (js == null)
2883 return null;
2884 HashMap jscript = PdfNameTree.readTree(js);
2885 String sortedNames[] = new String[jscript.size()];
2886 sortedNames = (String[])jscript.keySet().toArray(sortedNames);
2887 Arrays.sort(sortedNames);
2888 StringBuffer buf = new StringBuffer();
2889 for (int k = 0; k < sortedNames.length; ++k) {
2890 PdfDictionary j = (PdfDictionary)getPdfObjectRelease((PdfIndirectReference)jscript.get(sortedNames[k]));
2891 if (j == null)
2892 continue;
2893 PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS));
2894 if (obj != null) {
2895 if (obj.isString())
2896 buf.append(((PdfString)obj).toUnicodeString()).append('\n');
2897 else if (obj.isStream()) {
2898 byte bytes[] = getStreamBytes((PRStream)obj, file);
2899 if (bytes.length >= 2 && bytes[0] == (byte)254 && bytes[1] == (byte)255)
2900 buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_UNICODE));
2901 else
2902 buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_PDFDOCENCODING));
2903 buf.append('\n');
2904 }
2905 }
2906 }
2907 return buf.toString();
2908 }
2909
2910 /**
2911 * Gets the global document JavaScript.
2912 * @throws IOException on error
2913 * @return the global document JavaScript
2914 */
2915 public String getJavaScript() throws IOException {
2916 RandomAccessFileOrArray rf = getSafeFile();
2917 try {
2918 rf.reOpen();
2919 return getJavaScript(rf);
2920 }
2921 finally {
2922 try{rf.close();}catch(Exception e){}
2923 }
2924 }
2925
2926 /**
2927 * Selects the pages to keep in the document. The pages are described as
2928 * ranges. The page ordering can be changed but
2929 * no page repetitions are allowed. Note that it may be very slow in partial mode.
2930 * @param ranges the comma separated ranges as described in {@link SequenceList}
2931 */
2932 public void selectPages(String ranges) {
2933 selectPages(SequenceList.expand(ranges, getNumberOfPages()));
2934 }
2935
2936 /**
2937 * Selects the pages to keep in the document. The pages are described as a
2938 * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but
2939 * no page repetitions are allowed. Note that it may be very slow in partial mode.
2940 * @param pagesToKeep the pages to keep in the document
2941 */
2942 public void selectPages(List pagesToKeep) {
2943 pageRefs.selectPages(pagesToKeep);
2944 removeUnusedObjects();
2945 }
2946
2947 /** Sets the viewer preferences as the sum of several constants.
2948 * @param preferences the viewer preferences
2949 * @see PdfViewerPreferences#setViewerPreferences
2950 */
2951 public void setViewerPreferences(int preferences) {
2952 this.viewerPreferences.setViewerPreferences(preferences);
2953 setViewerPreferences(this.viewerPreferences);
2954 }
2955
2956 /** Adds a viewer preference
2957 * @param key a key for a viewer preference
2958 * @param value a value for the viewer preference
2959 * @see PdfViewerPreferences#addViewerPreference
2960 */
2961
2962 public void addViewerPreference(PdfName key, PdfObject value) {
2963 this.viewerPreferences.addViewerPreference(key, value);
2964 setViewerPreferences(this.viewerPreferences);
2965 }
2966
2967 void setViewerPreferences(PdfViewerPreferencesImp vp) {
2968 vp.addToCatalog(catalog);
2969 }
2970
2971 /**
2972 * @return an int that contains the Viewer Preferences.
2973 */
2974 public int getSimpleViewerPreferences() {
2975 return PdfViewerPreferencesImp.getViewerPreferences(catalog).getPageLayoutAndMode();
2976 }
2977
2978 /**
2979 * Getter for property appendable.
2980 * @return Value of property appendable.
2981 */
2982 public boolean isAppendable() {
2983 return this.appendable;
2984 }
2985
2986 /**
2987 * Setter for property appendable.
2988 * @param appendable New value of property appendable.
2989 */
2990 public void setAppendable(boolean appendable) {
2991 this.appendable = appendable;
2992 if (appendable)
2993 getPdfObject(trailer.get(PdfName.ROOT));
2994 }
2995
2996 /**
2997 * Getter for property newXrefType.
2998 * @return Value of property newXrefType.
2999 */
3000 public boolean isNewXrefType() {
3001 return newXrefType;
3002 }
3003
3004 /**
3005 * Getter for property fileLength.
3006 * @return Value of property fileLength.
3007 */
3008 public int getFileLength() {
3009 return fileLength;
3010 }
3011
3012 /**
3013 * Getter for property hybridXref.
3014 * @return Value of property hybridXref.
3015 */
3016 public boolean isHybridXref() {
3017 return hybridXref;
3018 }
3019
3020 static class PageRefs {
3021 private PdfReader reader;
3022 private IntHashtable refsp;
3023 private ArrayList refsn;
3024 private ArrayList pageInh;
3025 private int lastPageRead = -1;
3026 private int sizep;
3027 private boolean keepPages;
3028
3029 private PageRefs(PdfReader reader) throws IOException {
3030 this.reader = reader;
3031 if (reader.partial) {
3032 refsp = new IntHashtable();
3033 PdfNumber npages = (PdfNumber)PdfReader.getPdfObjectRelease(reader.rootPages.get(PdfName.COUNT));
3034 sizep = npages.intValue();
3035 }
3036 else {
3037 readPages();
3038 }
3039 }
3040
3041 PageRefs(PageRefs other, PdfReader reader) {
3042 this.reader = reader;
3043 this.sizep = other.sizep;
3044 if (other.refsn != null) {
3045 refsn = new ArrayList(other.refsn);
3046 for (int k = 0; k < refsn.size(); ++k) {
3047 refsn.set(k, duplicatePdfObject((PdfObject)refsn.get(k), reader));
3048 }
3049 }
3050 else
3051 this.refsp = (IntHashtable)other.refsp.clone();
3052 }
3053
3054 int size() {
3055 if (refsn != null)
3056 return refsn.size();
3057 else
3058 return sizep;
3059 }
3060
3061 void readPages() throws IOException {
3062 if (refsn != null)
3063 return;
3064 refsp = null;
3065 refsn = new ArrayList();
3066 pageInh = new ArrayList();
3067 iteratePages((PRIndirectReference)reader.catalog.get(PdfName.PAGES));
3068 pageInh = null;
3069 reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn.size()));
3070 }
3071
3072 void reReadPages() throws IOException {
3073 refsn = null;
3074 readPages();
3075 }
3076
3077 /** Gets the dictionary that represents a page.
3078 * @param pageNum the page number. 1 is the first
3079 * @return the page dictionary
3080 */
3081 public PdfDictionary getPageN(int pageNum) {
3082 PRIndirectReference ref = getPageOrigRef(pageNum);
3083 return (PdfDictionary)PdfReader.getPdfObject(ref);
3084 }
3085
3086 /**
3087 * @param pageNum
3088 * @return a dictionary object
3089 */
3090 public PdfDictionary getPageNRelease(int pageNum) {
3091 PdfDictionary page = getPageN(pageNum);
3092 releasePage(pageNum);
3093 return page;
3094 }
3095
3096 /**
3097 * @param pageNum
3098 * @return an indirect reference
3099 */
3100 public PRIndirectReference getPageOrigRefRelease(int pageNum) {
3101 PRIndirectReference ref = getPageOrigRef(pageNum);
3102 releasePage(pageNum);
3103 return ref;
3104 }
3105
3106 /** Gets the page reference to this page.
3107 * @param pageNum the page number. 1 is the first
3108 * @return the page reference
3109 */
3110 public PRIndirectReference getPageOrigRef(int pageNum) {
3111 try {
3112 --pageNum;
3113 if (pageNum < 0 || pageNum >= size())
3114 return null;
3115 if (refsn != null)
3116 return (PRIndirectReference)refsn.get(pageNum);
3117 else {
3118 int n = refsp.get(pageNum);
3119 if (n == 0) {
3120 PRIndirectReference ref = getSinglePage(pageNum);
3121 if (reader.lastXrefPartial == -1)
3122 lastPageRead = -1;
3123 else
3124 lastPageRead = pageNum;
3125 reader.lastXrefPartial = -1;
3126 refsp.put(pageNum, ref.getNumber());
3127 if (keepPages)
3128 lastPageRead = -1;
3129 return ref;
3130 }
3131 else {
3132 if (lastPageRead != pageNum)
3133 lastPageRead = -1;
3134 if (keepPages)
3135 lastPageRead = -1;
3136 return new PRIndirectReference(reader, n);
3137 }
3138 }
3139 }
3140 catch (Exception e) {
3141 throw new ExceptionConverter(e);
3142 }
3143 }
3144
3145 void keepPages() {
3146 if (refsp == null || keepPages)
3147 return;
3148 keepPages = true;
3149 refsp.clear();
3150 }
3151
3152 /**
3153 * @param pageNum
3154 */
3155 public void releasePage(int pageNum) {
3156 if (refsp == null)
3157 return;
3158 --pageNum;
3159 if (pageNum < 0 || pageNum >= size())
3160 return;
3161 if (pageNum != lastPageRead)
3162 return;
3163 lastPageRead = -1;
3164 reader.lastXrefPartial = refsp.get(pageNum);
3165 reader.releaseLastXrefPartial();
3166 refsp.remove(pageNum);
3167 }
3168
3169 /**
3170 *
3171 */
3172 public void resetReleasePage() {
3173 if (refsp == null)
3174 return;
3175 lastPageRead = -1;
3176 }
3177
3178 void insertPage(int pageNum, PRIndirectReference ref) {
3179 --pageNum;
3180 if (refsn != null) {
3181 if (pageNum >= refsn.size())
3182 refsn.add(ref);
3183 else
3184 refsn.add(pageNum, ref);
3185 }
3186 else {
3187 ++sizep;
3188 lastPageRead = -1;
3189 if (pageNum >= size()) {
3190 refsp.put(size(), ref.getNumber());
3191 }
3192 else {
3193 IntHashtable refs2 = new IntHashtable((refsp.size() + 1) * 2);
3194 for (Iterator it = refsp.getEntryIterator(); it.hasNext();) {
3195 IntHashtable.Entry entry = (IntHashtable.Entry)it.next();
3196 int p = entry.getKey();
3197 refs2.put(p >= pageNum ? p + 1 : p, entry.getValue());
3198 }
3199 refs2.put(pageNum, ref.getNumber());
3200 refsp = refs2;
3201 }
3202 }
3203 }
3204
3205 private void pushPageAttributes(PdfDictionary nodePages) {
3206 PdfDictionary dic = new PdfDictionary();
3207 if (!pageInh.isEmpty()) {
3208 dic.putAll((PdfDictionary)pageInh.get(pageInh.size() - 1));
3209 }
3210 for (int k = 0; k < pageInhCandidates.length; ++k) {
3211 PdfObject obj = nodePages.get(pageInhCandidates[k]);
3212 if (obj != null)
3213 dic.put(pageInhCandidates[k], obj);
3214 }
3215 pageInh.add(dic);
3216 }
3217
3218 private void popPageAttributes() {
3219 pageInh.remove(pageInh.size() - 1);
3220 }
3221
3222 private void iteratePages(PRIndirectReference rpage) throws IOException {
3223 PdfDictionary page = (PdfDictionary)getPdfObject(rpage);
3224 PdfArray kidsPR = (PdfArray)getPdfObject(page.get(PdfName.KIDS));
3225 if (kidsPR == null) {
3226 page.put(PdfName.TYPE, PdfName.PAGE);
3227 PdfDictionary dic = (PdfDictionary)pageInh.get(pageInh.size() - 1);
3228 PdfName key;
3229 for (Iterator i = dic.getKeys().iterator(); i.hasNext();) {
3230 key = (PdfName)i.next();
3231 if (page.get(key) == null)
3232 page.put(key, dic.get(key));
3233 }
3234 if (page.get(PdfName.MEDIABOX) == null) {
3235 PdfArray arr = new PdfArray(new float[]{0,0,PageSize.LETTER.getRight(),PageSize.LETTER.getTop()});
3236 page.put(PdfName.MEDIABOX, arr);
3237 }
3238 refsn.add(rpage);
3239 }
3240 else {
3241 page.put(PdfName.TYPE, PdfName.PAGES);
3242 pushPageAttributes(page);
3243 ArrayList kids = kidsPR.getArrayList();
3244 for (int k = 0; k < kids.size(); ++k){
3245 PdfObject obj = (PdfObject)kids.get(k);
3246 if (!obj.isIndirect()) {
3247 while (k < kids.size())
3248 kids.remove(k);
3249 break;
3250 }
3251 iteratePages((PRIndirectReference)obj);
3252 }
3253 popPageAttributes();
3254 }
3255 }
3256
3257 protected PRIndirectReference getSinglePage(int n) {
3258 PdfDictionary acc = new PdfDictionary();
3259 PdfDictionary top = reader.rootPages;
3260 int base = 0;
3261 while (true) {
3262 for (int k = 0; k < pageInhCandidates.length; ++k) {
3263 PdfObject obj = top.get(pageInhCandidates[k]);
3264 if (obj != null)
3265 acc.put(pageInhCandidates[k], obj);
3266 }
3267 PdfArray kids = (PdfArray)PdfReader.getPdfObjectRelease(top.get(PdfName.KIDS));
3268 for (Iterator it = kids.listIterator(); it.hasNext();) {
3269 PRIndirectReference ref = (PRIndirectReference)it.next();
3270 PdfDictionary dic = (PdfDictionary)getPdfObject(ref);
3271 int last = reader.lastXrefPartial;
3272 PdfObject count = getPdfObjectRelease(dic.get(PdfName.COUNT));
3273 reader.lastXrefPartial = last;
3274 int acn = 1;
3275 if (count != null && count.type() == PdfObject.NUMBER)
3276 acn = ((PdfNumber)count).intValue();
3277 if (n < base + acn) {
3278 if (count == null) {
3279 dic.mergeDifferent(acc);
3280 return ref;
3281 }
3282 reader.releaseLastXrefPartial();
3283 top = dic;
3284 break;
3285 }
3286 reader.releaseLastXrefPartial();
3287 base += acn;
3288 }
3289 }
3290 }
3291
3292 private void selectPages(List pagesToKeep) {
3293 IntHashtable pg = new IntHashtable();
3294 ArrayList finalPages = new ArrayList();
3295 int psize = size();
3296 for (Iterator it = pagesToKeep.iterator(); it.hasNext();) {
3297 Integer pi = (Integer)it.next();
3298 int p = pi.intValue();
3299 if (p >= 1 && p <= psize && pg.put(p, 1) == 0)
3300 finalPages.add(pi);
3301 }
3302 if (reader.partial) {
3303 for (int k = 1; k <= psize; ++k) {
3304 getPageOrigRef(k);
3305 resetReleasePage();
3306 }
3307 }
3308 PRIndirectReference parent = (PRIndirectReference)reader.catalog.get(PdfName.PAGES);
3309 PdfDictionary topPages = (PdfDictionary)PdfReader.getPdfObject(parent);
3310 ArrayList newPageRefs = new ArrayList(finalPages.size());
3311 PdfArray kids = new PdfArray();
3312 for (int k = 0; k < finalPages.size(); ++k) {
3313 int p = ((Integer)finalPages.get(k)).intValue();
3314 PRIndirectReference pref = getPageOrigRef(p);
3315 resetReleasePage();
3316 kids.add(pref);
3317 newPageRefs.add(pref);
3318 getPageN(p).put(PdfName.PARENT, parent);
3319 }
3320 AcroFields af = reader.getAcroFields();
3321 boolean removeFields = (af.getFields().size() > 0);
3322 for (int k = 1; k <= psize; ++k) {
3323 if (!pg.containsKey(k)) {
3324 if (removeFields)
3325 af.removeFieldsFromPage(k);
3326 PRIndirectReference pref = getPageOrigRef(k);
3327 int nref = pref.getNumber();
3328 reader.xrefObj.set(nref, null);
3329 if (reader.partial) {
3330 reader.xref[nref * 2] = -1;
3331 reader.xref[nref * 2 + 1] = 0;
3332 }
3333 }
3334 }
3335 topPages.put(PdfName.COUNT, new PdfNumber(finalPages.size()));
3336 topPages.put(PdfName.KIDS, kids);
3337 refsp = null;
3338 refsn = newPageRefs;
3339 }
3340 }
3341
3342 PdfIndirectReference getCryptoRef() {
3343 if (cryptoRef == null)
3344 return null;
3345 return new PdfIndirectReference(0, cryptoRef.getNumber(), cryptoRef.getGeneration());
3346 }
3347
3348 /**
3349 * Removes any usage rights that this PDF may have. Only Adobe can grant usage rights
3350 * and any PDF modification with iText will invalidate them. Invalidated usage rights may
3351 * confuse Acrobat and it's advisable to remove them altogether.
3352 */
3353 public void removeUsageRights() {
3354 PdfDictionary perms = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
3355 if (perms == null)
3356 return;
3357 perms.remove(PdfName.UR);
3358 perms.remove(PdfName.UR3);
3359 if (perms.size() == 0)
3360 catalog.remove(PdfName.PERMS);
3361 }
3362
3363 /**
3364 * Gets the certification level for this document. The return values can be <code>PdfSignatureAppearance.NOT_CERTIFIED</code>,
3365 * <code>PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED</code>,
3366 * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING</code> and
3367 * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>.
3368 * <p>
3369 * No signature validation is made, use the methods available for that in <CODE>AcroFields</CODE>.
3370 * </p>
3371 * @return gets the certification level for this document
3372 */
3373 public int getCertificationLevel() {
3374 PdfDictionary dic = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
3375 if (dic == null)
3376 return PdfSignatureAppearance.NOT_CERTIFIED;
3377 dic = (PdfDictionary)getPdfObject(dic.get(PdfName.DOCMDP));
3378 if (dic == null)
3379 return PdfSignatureAppearance.NOT_CERTIFIED;
3380 PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.REFERENCE));
3381 if (arr == null || arr.size() == 0)
3382 return PdfSignatureAppearance.NOT_CERTIFIED;
3383 dic = (PdfDictionary)getPdfObject((PdfObject)(arr.getArrayList().get(0)));
3384 if (dic == null)
3385 return PdfSignatureAppearance.NOT_CERTIFIED;
3386 dic = (PdfDictionary)getPdfObject(dic.get(PdfName.TRANSFORMPARAMS));
3387 if (dic == null)
3388 return PdfSignatureAppearance.NOT_CERTIFIED;
3389 PdfNumber p = (PdfNumber)getPdfObject(dic.get(PdfName.P));
3390 if (p == null)
3391 return PdfSignatureAppearance.NOT_CERTIFIED;
3392 return p.intValue();
3393 }
3394
3395 /**
3396 * Checks if the document was opened with the owner password so that the end application
3397 * can decide what level of access restrictions to apply. If the document is not encrypted
3398 * it will return <CODE>true</CODE>.
3399 * @return <CODE>true</CODE> if the document was opened with the owner password or if it's not encrypted,
3400 * <CODE>false</CODE> if the document was opened with the user password
3401 */
3402 public final boolean isOpenedWithFullPermissions() {
3403 return !encrypted || ownerPasswordUsed;
3404 }
3405
3406 public int getCryptoMode() {
3407 if (decrypt == null)
3408 return -1;
3409 else
3410 return decrypt.getCryptoMode();
3411 }
3412
3413 public boolean isMetadataEncrypted() {
3414 if (decrypt == null)
3415 return false;
3416 else
3417 return decrypt.isMetadataEncrypted();
3418 }
3419
3420 public byte[] computeUserPassword() {
3421 if (!encrypted || !ownerPasswordUsed) return null;
3422 return decrypt.computeUserPassword(password);
3423 }
3424 }