From 6025b6016517c6d898d8957d1d7e03ba71431912 Mon Sep 17 00:00:00 2001 From: tknall Date: Fri, 1 Dec 2006 12:20:24 +0000 Subject: Initial import of release 2.2. git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../wag/egov/egiz/pdf/AbsoluteTextSignature.java | 656 ++++++++ .../wag/egov/egiz/pdf/BinaryBlockInfo.java | 53 + .../wag/egov/egiz/pdf/BinarySignature.java | 1731 ++++++++++++++++++++ .../wag/egov/egiz/pdf/BinarySignatureHolder.java | 146 ++ .../at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java | 189 +++ .../egiz/pdf/IncrementalUpdateInformation.java | 152 ++ .../at/knowcenter/wag/egov/egiz/pdf/PDFPage.java | 539 ++++++ .../wag/egov/egiz/pdf/PDFSignatureCreation.java | 170 ++ .../wag/egov/egiz/pdf/PDFSignatureObject.java | 50 + .../wag/egov/egiz/pdf/PDFSignatureObjectIText.java | 490 ++++++ .../knowcenter/wag/egov/egiz/pdf/PDFUtilities.java | 89 + .../knowcenter/wag/egov/egiz/pdf/Placeholder.java | 552 +++++++ .../java/at/knowcenter/wag/egov/egiz/pdf/Pos.java | 62 + .../knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java | 64 + .../wag/egov/egiz/pdf/SignatureHolder.java | 62 + .../knowcenter/wag/egov/egiz/pdf/SplitStrings.java | 162 ++ .../knowcenter/wag/egov/egiz/pdf/StringInfo.java | 93 ++ .../at/knowcenter/wag/egov/egiz/pdf/TablePos.java | 56 + .../wag/egov/egiz/pdf/TextualSignature.java | 177 ++ .../wag/egov/egiz/pdf/TextualSignatureHolder.java | 73 + .../at/knowcenter/wag/egov/egiz/pdf/Utils.java | 96 ++ 21 files changed, 5662 insertions(+) create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java create mode 100644 src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java (limited to 'src/main/java/at/knowcenter/wag/egov/egiz/pdf') diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java new file mode 100644 index 0000000..5523041 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java @@ -0,0 +1,656 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: AbsoluteTextSignature.java,v 1.1 2006/10/31 08:08:33 wprinz Exp $ + */package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Vector; + +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.PdfAS; +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureException; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureTypesException; +import at.knowcenter.wag.egov.egiz.framework.FoundBlock; +import at.knowcenter.wag.egov.egiz.framework.FoundKey; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypeDefinition; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; + +/** + * Contains methods and helpers that implement the absolute text signature. + * @author wprinz + */ +public class AbsoluteTextSignature +{ + + /** + * The logger definition. + */ + private static final Logger logger = ConfigLogger.getLogger(AbsoluteTextSignature.class); + + + /** + * Extracts all signature holders from a given text. + * + *

+ * First the latest signature holder is extracted. Then the latest signature + * holder in the rest text, which is the second latest one, is extracted. Then + * the third latest signature holder is extracted and so forth until no more + * signature holders are found. + *

+ * + * @param text + * The text. + * @return Returns the List of extracted signature holders ordered by their + * date ascendingly (the lowest, earliest date first, the latest, + * newest date last). An empty list is returned if no signature + * holders were found. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static List extractSignatureHoldersFromText(String text) throws SignatureException, SignatureTypesException + { + List holders = new ArrayList(); + + String current_text = text; + for (;;) + { + SignatureHolder signature_holder = extractLatestBlock(current_text); + if (signature_holder == null) + { + break; + } + + holders.add(0, signature_holder); + + current_text = signature_holder.getSignedText(); + } + + return holders; + } + + /** + * Extracts the latest signature block from the given text and creates a + * SignatureHolder object that can be verified. + * + * @param text + * The text. + * @return Returns the SignatureObject extracted from the text, or null, if no + * latest block was found. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static SignatureHolder extractLatestBlock(String text) throws SignatureException, SignatureTypesException + { + FoundBlock latest_block = findLatestBlock(text); + if (latest_block == null) + { + return null; + } + + String reconstructed_text = cutOutBlock(text, latest_block); + + SignatureObject so = createSignatureObjectFromFoundBlock(text, latest_block); + TextualSignatureHolder tsh = new TextualSignatureHolder(reconstructed_text, so); + + return tsh; + } + + /** + * Finds the latest signature block for a given text. + * + *

+ * The latest block is the one with the highest, most recent date. Usually + * this block will be extracted (cut out) of the text which will result in the + * originally signed text of this signature to be verified using the cut out + * data. + *

+ * + * @param text + * The text to be analyzed. + * @return Returns the latest found block or null, if there was none. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static FoundBlock findLatestBlock(String text) throws SignatureException, SignatureTypesException + { +// try +// { +// writeTextToFile(text, new File("C:\\wprinz\\text.utf8.txt")); +// } +// catch (IOException e) +// { +// e.printStackTrace(); +// } + + SignatureTypes sig_types = SignatureTypes.getInstance(); + List signatureTypes_ = sig_types.getSignatureTypeDefinitions(); + + List found_candidates = new ArrayList(); + + for (int i = 0; i < signatureTypes_.size(); i++) + { + SignatureTypeDefinition block_type = (SignatureTypeDefinition) signatureTypes_.get(i); + List found_candidates_for_type = findPotentialSignaturesForProfile(text, block_type); + + found_candidates.addAll(found_candidates_for_type); + } + + if (found_candidates.isEmpty()) + { + logger.debug("no candidates found at all"); + return null; + } + + logger.debug("checking block integrity"); + for (int i = 0; i < found_candidates.size(); i++) + { + FoundBlock found_block = (FoundBlock) found_candidates.get(i); + + String date_value = getDateValue(text, found_block); + logger.debug("date_value = " + date_value); + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug("found_block = " + date + " - " + found_block); + + checkBlockIntegrity(text, found_block); + } + + sortFoundBlocksByDate(text, found_candidates); + + logger.debug("sorted blocks:"); + for (int i = 0; i < found_candidates.size(); i++) + { + FoundBlock found_block = (FoundBlock) found_candidates.get(i); + + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug(" #" + i + ": " + date + " - " + found_block); + } + + List latest_blocks = filterLastDateEqualBlocks(text, found_candidates); + logger.debug("latest blocks:"); + for (int i = 0; i < latest_blocks.size(); i++) + { + FoundBlock found_block = (FoundBlock) latest_blocks.get(i); + + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug(" #" + i + ": " + date + " - " + found_block); + } + + boolean semantic_equality = PdfAS.checkForSemanticEquality(latest_blocks); + logger.debug("semantic_equality = " + semantic_equality); + if (!semantic_equality) + { + throw new SignatureException(314, "The latest blocks weren't semantically equal."); + } + + FoundBlock latest_block = (FoundBlock) latest_blocks.get(0); + logger.debug("latest block = " + latest_block); + return latest_block; + } + + /** + * Finds the List of potential blocks within the given text for the given + * profile. + * + * @param text + * The text, in which potential block are to be sought. + * @param block_type + * The profile for which the text is to be sought. + * @return Returns the List of potential FoundBlocks or an empty List if none + * could be found. + */ + public static List findPotentialSignaturesForProfile(String text, + SignatureTypeDefinition block_type) + { + logger.debug("find potential signatures for " + block_type.getType()); + + List found_blocks = new ArrayList(); + + final boolean old_style = false; + + Vector keys = block_type.getRevertSortedKeys(); + Vector captions = block_type.getRevertSortedCaptions(); + + String last_key = (String) keys.get(0); + logger.debug("last_key = " + last_key); + String last_caption = (String) captions.get(0); + logger.debug("last_caption = " + last_caption); + + List found_last_captions = findIndices(text, last_caption); + if (logger.isDebugEnabled()) + { + logger.debug("found " + found_last_captions.size() + " last captions."); + for (int i = 0; i < found_last_captions.size(); i++) + { + logger.debug(" found last caption at index " + found_last_captions.get(i)); + } + } + + for (int lci = 0; lci < found_last_captions.size(); lci++) + { + int last_caption_index = ((Integer) found_last_captions.get(lci)).intValue(); + logger.debug("resolving signature block from last caption index " + last_caption_index); + + int potential_block_end = findEndOfValue(text, last_caption_index); + logger.debug("potential_block_end = " + potential_block_end); + + List found_keys = PdfAS.findBlockInText(text.substring(0, potential_block_end), block_type, old_style); // findRestKeys(text, + // keys, + // captions, + // last_caption_index); + + if (found_keys == null) + { + logger.debug("Not all other captions could be found for the last_caption_index " + last_caption_index + " ==> discarding this index."); + + continue; + } + + // sort found keys ascendingly + PdfAS.sortFoundKeysAscendingly(found_keys); + + boolean reverse_check_ok = reverseCheckFoundKeys(text, found_keys); + if (!reverse_check_ok) + { + logger.debug("The reverse check ruled this list of found keys out ==> they are discarded."); + + continue; + } + + logger.debug("The reverse check proved this list of found keys out ==> adding them as potential candidates."); + + FoundBlock found_block = new FoundBlock(); + found_block.std = block_type; + found_block.found_keys = found_keys; + found_block.end_index = findEndOfValue(text, last_caption_index); + found_blocks.add(found_block); + } + + logger.debug("found " + found_blocks.size() + " potential signatures for " + block_type.getType()); + return found_blocks; + } + + /** + * Finds all indices of the given subtext within a given text. + * + *

+ * This is usually used to find the indices of the last captions. + *

+ * + * @param text + * The text to be searched. + * @param subtext + * The subtext to be sought. + * @return Returns the List of found indices. + */ + public static List findIndices(String text, String subtext) + { + List found_indices = new ArrayList(); + int search_from_index = 0; + for (;;) + { + int found_index = text.indexOf(subtext, search_from_index); + if (found_index < 0) + { + break; + } + found_indices.add(new Integer(found_index)); + search_from_index = found_index + subtext.length(); + } + return found_indices; + } + + /** + * Finds the other keys/captions according to their order starting from the + * last_caption index upwards. + * + * @param text + * The text. + * @param keys + * The list of keys. + * @param captions + * The list of captions. + * @param last_caption_index + * The index of the last caption. + * @return Returns the List of found keys, if all keys could be found, or null + * if not all keys could be found. + */ + public static List findRestKeys(String text, List keys, List captions, + int last_caption_index) + { + List found_keys = new ArrayList(); + + FoundKey last_caption_found_key = new FoundKey((String) keys.get(0), (String) captions.get(0), last_caption_index); + found_keys.add(last_caption_found_key); + + String rest_text = text.substring(0, last_caption_index); + + for (int i = 1; i < captions.size(); i++) + { + String sought_caption = (String) captions.get(i); + int index = rest_text.lastIndexOf(sought_caption); + + if (index < 0) + { + return null; + } + + FoundKey found_key = new FoundKey((String) keys.get(i), (String) captions.get(i), index); + found_keys.add(0, found_key); + + rest_text = rest_text.substring(0, index); + } + + return found_keys; + } + + /** + * Performs a reverse (top to bottom) search for the found keys and checks + * that these indices are the same as those that were found during the regular + * (bottom up) search. + *

+ * If a reverse check proves that the found keys are not at the same positions + * as during regular search, this list of found keys should be discarded. + *

+ * + * @param text + * The text. + * @param found_keys + * The found keys to be reversely checked. + * @return Returns true, if all (also the non required) captions could be + * found at the same indices as during regular search, false + * otherwise. + */ + public static boolean reverseCheckFoundKeys(String text, List found_keys) + { + int search_from_index = ((FoundKey) found_keys.get(0)).start_index; + for (int i = 0; i < found_keys.size(); i++) + { + FoundKey found_key = (FoundKey) found_keys.get(i); + + int reverse_found_index = text.indexOf(found_key.caption, search_from_index); + if (reverse_found_index < 0) + { + throw new RuntimeException("The caption " + found_key.caption + " wasn't found in the text during reverse checking - there is something wrong."); + } + + if (reverse_found_index != found_key.start_index) + { + logger.debug("The index for caption " + found_key.caption + " wasn't proved during reverse checking."); + return false; + } + + search_from_index = found_key.start_index + found_key.caption.length(); + } + + return true; + } + + /** + * Finds the end of the value in the text. + * + *

+ * This simply scans for a '\n' from a given start index. The line up to and + * inclusive the '\n' is considered to be the value. + *

+ *

+ * Note that this method does NOT find the accurate value, if the value goes + * over multiple lines! This may bear a serious problem. Usually this method + * is only used to finding the end of the last value in a found block, because + * mid- values are exactly determined by their start index and the start of + * the next caption. Nevertheless, if the last value spans over multiple + * lines, this method will not retrieve it completely. + *

+ * + * @param text + * The text. + * @param start_index + * The start index from where the end of the value is sought. + * @return Returns the end index of the value, which is the index of the first + * character not belonging to the value anymore (the character after + * the '\n'). + */ + public static int findEndOfValue(String text, int start_index) + { + int newline_index = text.indexOf('\n', start_index); + if (newline_index < 0) + { + return text.length(); + } + return newline_index + 1; + } + + /** + * Checks the integrity of a found block. + * + *

+ * This is an assertive function. + *

+ * + * @param text + * The text. + * @param found_block + * The found block. + */ + public static void checkBlockIntegrity(String text, FoundBlock found_block) + { + for (int i = 0; i < found_block.found_keys.size() - 1; i++) + { + FoundKey this_key = (FoundKey) found_block.found_keys.get(i); + FoundKey next_key = (FoundKey) found_block.found_keys.get(i + 1); + + int this_end_index = findEndOfValue(text, this_key.start_index); + if (this_end_index != next_key.start_index) + { + logger.warn("multi line value: " + this_key); + // throw new RuntimeException("The end index of found key " + this_key + + // " doesn't match the start index of found key " + next_key); + } + } + + FoundKey last_key = (FoundKey) found_block.found_keys.get(found_block.found_keys.size() - 1); + if (findEndOfValue(text, last_key.start_index) != found_block.end_index) + { + throw new RuntimeException("The end index of last key " + last_key + " doesn't match the end index of the block " + found_block); + } + + } + + /** + * Cuts out the given found block from the text. + * + * @param text + * The text. + * @param block + * The found block. + * @return Returns the rest text without the block. + */ + public static String cutOutBlock(String text, FoundBlock block) + { + int block_start_index = ((FoundKey) block.found_keys.get(0)).getStartIndex(); + int block_end_index = block.end_index; + + if (block_end_index == text.length()) + { + // if the block is at the end of the text, remove the "\n" before the + // block as well. + String pre = text.substring(0, block_start_index - 1); + return pre; + } + + String pre = text.substring(0, block_start_index); + String post = text.substring(block_end_index); + + String rest_text = pre + post; + return rest_text; + } + + /** + * Returns the value of the date field as String. + * + * @param text + * The text. + * @param block + * The found block. + * @return Returns the date value. + */ + public static String getDateValue(String text, FoundBlock block) + { + FoundKey date_key = block.getDateFoundKey(); + int date_value_start_index = date_key.start_index + date_key.caption.length(); + int date_value_end_index = findEndOfValue(text, date_value_start_index); + String date_value = text.substring(date_value_start_index, date_value_end_index).trim(); + + return date_value; + } + + /** + * Creates a SignatureObject from a found block by extracting the + * corresponding values. + * + * @param text + * The text. + * @param found_block + * The found block. + * @return Returns the created SignatureObject. + * @throws SignatureTypesException + * F.e. + * @throws SignatureException + * F.e. + */ + public static SignatureObject createSignatureObjectFromFoundBlock( + String text, FoundBlock found_block) throws SignatureTypesException, SignatureException + { + SignatureObject signatureObject = new SignatureObject(); + + signatureObject.setSigType(found_block.std.getType()); + signatureObject.initByType(); + + int end_index = found_block.end_index; + for (int i = found_block.found_keys.size() - 1; i >= 0; i--) + { + FoundKey cur_key = (FoundKey) found_block.found_keys.get(i); + int start_index = cur_key.getStartIndex() + cur_key.caption.length(); + + String value = text.substring(start_index, end_index); + + signatureObject.setSigValueCaption(cur_key.getKey(), value, cur_key.caption); + + end_index = cur_key.getStartIndex(); + } + + return signatureObject; + + } + + /** + * Parses the EGIZDate from a found block and the given text. + * + * @param text + * The text. + * @param found_block + * The found block. + * @return Returns the parsed EGIZDate. + */ + public static EGIZDate getDateFromFoundBlock(String text, + FoundBlock found_block) + { + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + return date; + } + + /** + * Sorts the List of found blocks by date. + * + * @param text + * The text. + * @param found_blocks + * The List of found blocks. + */ + public static void sortFoundBlocksByDate(final String text, List found_blocks) + { + Collections.sort(found_blocks, new Comparator() + { + public int compare(Object arg0, Object arg1) + { + FoundBlock fb0 = (FoundBlock) arg0; + FoundBlock fb1 = (FoundBlock) arg1; + + EGIZDate date0 = getDateFromFoundBlock(text, fb0); + EGIZDate date1 = getDateFromFoundBlock(text, fb1); + return date0.compareTo(date1); + } + }); + } + + /** + * Given a List of FoundBlock objects, this method returns the last blocks of + * this list that have the same date. + * + *

+ * Usually a date sorted list (earliest first, latest last) will be provided + * to this method. Then the last date equal blocks are returned, which are the + * last blocks. + *

+ * + * @param text + * The text to retrieve the values of the fields from. + * @param found_blocks + * The List of FoundBlock objects. + * @return Returns the List of the last date equal blocks. + */ + public static List filterLastDateEqualBlocks(String text, List found_blocks) + { + List latest_blocks = new ArrayList(); + + latest_blocks.add(found_blocks.get(found_blocks.size() - 1)); + + for (int i = found_blocks.size() - 2; i >= 0; i--) + { + FoundBlock this_block = (FoundBlock) found_blocks.get(i); + FoundBlock succ_block = (FoundBlock) found_blocks.get(i + 1); + + EGIZDate this_date = getDateFromFoundBlock(text, this_block); + EGIZDate succ_date = getDateFromFoundBlock(text, succ_block); + + if (!this_date.equals(succ_date)) + { + break; + } + latest_blocks.add(0, this_block); + } + + return latest_blocks; + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java new file mode 100644 index 0000000..cdc092f --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: BinaryBlockInfo.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.List; + +/** + * Helper class that holds information about a binary signature block. + * + * @author wprinz + */ +public class BinaryBlockInfo +{ + /** + * The signed size, in bytes. + * + *

+ * This includes the block itself. + *

+ */ + public int signed_size = -1; + + /** + * The List of ReplaceInfo objects that specify the replaced strings. + */ + public List replaces = null; + +// /** +// * The start of the /ODS number in the PDF. +// */ +// public int ods_start = -1; +// +// /** +// * The start of the \replaces array in the PDF. +// */ +// public int array_start = -1; + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java new file mode 100644 index 0000000..c5acbc4 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java @@ -0,0 +1,1731 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: BinarySignature.java,v 1.4 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingNotFoundException; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureException; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureTypesException; +import at.knowcenter.wag.egov.egiz.sig.SignatureBlock; +import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; +import at.knowcenter.wag.egov.egiz.sig.X509Cert; +import at.knowcenter.wag.egov.egiz.tools.CodingHelper; +import at.knowcenter.wag.exactparser.ByteArrayUtils; + +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.PRStream; +import com.lowagie.text.pdf.PdfArray; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfDictionary; +import com.lowagie.text.pdf.PdfIndirectObject; +import com.lowagie.text.pdf.PdfIndirectReference; +import com.lowagie.text.pdf.PdfName; +import com.lowagie.text.pdf.PdfNumber; +import com.lowagie.text.pdf.PdfObject; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.PdfReader; +import com.lowagie.text.pdf.PdfStamper; +import com.lowagie.text.pdf.PdfStamperImp; +import com.lowagie.text.pdf.PdfString; +import com.lowagie.text.pdf.PdfTemplate; + +/** + * Contains various extension functions to digitally sign documents. + * + *

+ * These functions are used to replace parts of the original Egiz plain text + * signature mechanism. + *

+ * + * @author wprinz + */ +public abstract class BinarySignature +{ + /** + * The tolerance area of the line break algorithm. + * + * @see Placeholder#replacePlaceholderWithTolerance(byte[], List, byte[], int) + */ + public static final int LINE_BREAK_TOLERANCE = 10; + + /** + * The number of bytes left out for the certificate placeholder. + */ + public static final int CERTIFICATE_PLACEHOLDER_LENGTH = 10000; + + /** + * The placeholder character used to fill out Strings in the layout process. + */ + public static final byte LAYOUT_PLACEHOLDER = 'w'; + + /** + * This placeholder is used to fill out holes between the byte ranges before + * the document is signed. + */ + public static final byte SIGN_PLACEHOLDER = 0; + + /** + * The nil brev used to define an unrecognized value. + */ + public static final byte[] BREV_NIL = { 'n', 'i', 'l' }; + + /** + * The date brev. + */ + public static final byte[] BREV_DAT = { 'd', 'a', 't' }; + + /** + * The issure brev. + */ + public static final byte[] BREV_ISS = { 'i', 's', 's' }; + + /** + * The serial number brev. + */ + public static final byte[] BREV_SNR = { 's', 'n', 'r' }; + + /** + * The value brev. + */ + public static final byte[] BREV_VAL = { 'v', 'a', 'l' }; + + /** + * The SIG_ID brev. + */ + public static final byte[] BREV_SID = { 's', 'i', 'd' }; + + /** + * No explicit encoding. + */ + public static final byte[] ENCODING_NIL = { 'n', 'i', 'l' }; + + /** + * PDF WinAnsiEncoding. + */ + public static final byte[] ENCODING_WIN = { 'w', 'i', 'n' }; + + /** + * URL encoding. + */ + public static final byte[] ENCODING_URL = { 'u', 'r', 'l' }; + + /** + * The PDFName of the Egiz Dictionary. + * + *

+ * Used to locate and identify the Egiz Dictionary in the document. + *

+ */ + public static final PdfName EGIZ_DICT_NAME = new PdfName("EGIZSigDict"); + + /** + * The PDFName of the Original Document Size (ODS) field in an Egiz + * Dictionary. + * + *

+ * The ODS must be a positive integral number. + *

+ */ + public static final PdfName EGIZ_ODS_NAME = new PdfName("ODS"); + + /** + * The PDFName of the Kennzeichnung attribute. + */ + public static final PdfName EGIZ_KZ_NAME = new PdfName("ID"); + + /** + * The PDFName of the /replaces field in an Egiz Dictionary. + */ + public static final PdfName EGIZ_REPLACES_NAME = new PdfName("replaces"); + + /** + * The PDFName of the /encodings field in an Egiz Dictionary. + */ + public static final PdfName EGIZ_ENCODINGS_NAME = new PdfName("encodings"); + + /** + * The PDFName of the byte ranges array. + */ + public static final PdfName EGIZ_BYTERANGES_NAME = new PdfName("ByteRange"); + + /** + * The PdfName of the certificate array. + */ + public static final PdfName EGIZ_CERTIFICATE_NAME = new PdfName("Cert"); + + /** + * The PDFName of the Signature XObject field in an Egiz Dictionary. + * + *

+ * This must be an indirect reference to the XObject containing the Signature + * table. + *

+ */ + public static final PdfName EGIZ_XOBJ_NAME = new PdfName("SigXObject"); + + /** + * The number placeholder that is used to give numbers a fixed length. + */ + protected static final PdfNumber NUMBER_PLACEHOLDER = new PdfNumber(99999999); + + /** + * Extracts the signature text only. + * + *

+ * The signature text is the text of the Signature XObject. + *

+ * + * @param egiz_dict + * The Egiz Dictionary. + * + * @return Returns the signature text. + */ + public static String extractSignatureTextOnly(PdfDictionary egiz_dict) throws IOException + { + PdfIndirectReference xobj_ir = (PdfIndirectReference) egiz_dict.get(EGIZ_XOBJ_NAME); + PRStream temp_stream = (PRStream) PdfReader.getPdfObject(xobj_ir); + + byte[] stream_bytes = PdfReader.getStreamBytes(temp_stream); + + return Utils.extractPureTextFromContentStream(stream_bytes); + } + + /** + * Retrieves the size of the original document from the Egiz Dictionary. + * + * @param egiz_dict + * The Egiz Dictionary. + * @return Returns the size (in bytes) of the original document. + */ + public static int getOriginalDocumentSizeFromEgizDict(PdfDictionary egiz_dict) + { + PdfObject ods_obj = egiz_dict.get(EGIZ_ODS_NAME); + PdfNumber ods_number = (PdfNumber) PdfReader.getPdfObject(ods_obj); + + return ods_number.intValue(); + } + + /** + * Retrieves the previous Egiz dictionary from the given one, if a previous + * dictionary exists. + * + * @param egiz_dict + * The Egiz Dictionary. + * @return Returns the previous Egiz Dictionary, or null if there is none. + */ + public static PdfDictionary getPreviousFromEgizDict(PdfDictionary egiz_dict) + { + PdfObject prev_obj = egiz_dict.get(PdfName.PREV); + PdfDictionary previous_dict = (PdfDictionary) PdfReader.getPdfObject(prev_obj); + return previous_dict; + } + + /** + * Retrieves the Egiz Dictionary from the document if present. + * + * @param reader + * The reader to retrieve the dictionary from. + * @return Returns the Egiz Dictionary, if present, or returns null, if no + * egiz dictionary was found. + */ + public static PdfDictionary getEgizDictFromReader(PdfReader reader) + { + PdfIndirectReference dict_ir = getEgizDictIndRefFromReader(reader); + if (dict_ir == null) + { + return null; + } + + PdfDictionary egiz_dict = (PdfDictionary) PdfReader.getPdfObject(dict_ir); + + return egiz_dict; + } + + /** + * Retrieves the Egiz Dictionary's indirect reference from the reader. + * + * @param reader + * The reader. + * @return Returns the indirect reference of the Egiz Dictionary, or null, if + * none exists. + */ + public static PdfIndirectReference getEgizDictIndRefFromReader( + PdfReader reader) + { + PdfDictionary catalog = reader.getCatalog(); + PdfIndirectReference dict_ir = (PdfIndirectReference) catalog.get(EGIZ_DICT_NAME); + return dict_ir; + } + + /** + * Retrieves the chain of Egiz Dictionaries from the reader. + * + *

+ * The first element in the List will be the top most (oldest) Egiz + * Dictionary. The last element in the List will be the bottom most (latest) + * Egiz Dictionary. If the list is empty, no dictionary could be found at all, + * which means that the document is not digitally signed. + *

+ * + * @param reader + * The reader. + * @return Returns the List of PdfDictionaries from the document. + */ + public static List getEgizDictChainFromReader(PdfReader reader) + { + List dicts = new ArrayList(); + + PdfDictionary current_dict = getEgizDictFromReader(reader); + if (current_dict != null) + { + dicts.add(0, current_dict); + + while ((current_dict = getPreviousFromEgizDict(current_dict)) != null) + { + dicts.add(0, current_dict); + } + } + + return dicts; + } + + /** + * Builds a digest of the given data. + * + * @param data + * The data to be digested. + * @param length + * The length of the data portion that should be used for digesting. + * This allows to build the digest only over parts of the data. + * @return Returns the created digest. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static byte[] buildDigest(final byte[] data, final int length) throws PDFDocumentException + { + MessageDigest sha_512 = null; + try + { + sha_512 = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + throw new PDFDocumentException(202, "Digest algorithm not supported - NoSuchAlgorithmException", e); + } + + sha_512.reset(); + sha_512.update(data, 0, length); + byte[] digest = sha_512.digest(); + + return digest; + } + + /** + * Retrieves the signable text from the given document. + * + * @param data + * The data. + * @param ods + * The original document size. + * @return Returns the signable text. + */ + public static String retrieveSignableTextFromData(final byte[] data, + final int ods) + { + // byte[] digest = buildDigest(data, ods); + String raw_text = CodingHelper.encodeBase64(data);// digest); // data); + return raw_text; + } + + /** + * Fills the holes in the byte ranges with the SIGN_PLACEHOLDER. + * + * @param data + * The given byte ranged data. + * @param byte_ranges + * The byte ranges. + * @return Returns the filled text. + */ + public static byte[] prepareDataToSign(final byte[] data, + final List byte_ranges) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Iterator it = byte_ranges.iterator(); + int last_end = 0; + while (it.hasNext()) + { + StringInfo si = (StringInfo) it.next(); + + for (int i = last_end; i < si.string_start; i++) + { + baos.write(SIGN_PLACEHOLDER); + } + + baos.write(data, si.string_start, si.string_length); + + last_end = si.string_start + si.string_length; + } + byte[] data_to_sign = baos.toByteArray(); + + return data_to_sign; + } + + /** + * Extracts the binary 'text' of a document. + * + *

+ * If the document contains an Egiz Dictionary, which means that it is already + * signed, the binary text is the Base64 coded string of the original document + * followed by the Ascii representation of the signature block. + *

+ *

+ * If the document does not contain an Egiz Dictionary, which means that it is + * unsigned, only the binary Base64 coded original document is returned as + * binary text. + *

+ *

+ * This function is intented for being used instead of the "text extraction" + * mechanism used in the plain text Egiz project. + *

+ * + * @param doc + * The file. + * @return Returns the binary text of the document. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static String extractTextBinary(File doc) throws PDFDocumentException + { + try + { + FileInputStream fis = new FileInputStream(doc); + return extractTextBinary(fis); + } + catch (FileNotFoundException e) + { + throw new PDFDocumentException(202, e); + } + } + + /** + * Extracts the text binary. + * + * @param is + * @return Returns the binary text. + * @throws PDFDocumentException + */ + public static String extractTextBinary(InputStream is) throws PDFDocumentException + { + try + { + // for some stupid reason this produces a read error if the is comes from + // a + // multipart servlet form..??? + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int i = -1; + int acc = 0; + byte[] b = new byte[1000]; + while ((i = is.read(b)) > 0) + { + acc += i; + System.out.print(" " + i); + baos.write(b, 0, i); + } + System.out.println("acc = " + acc); + byte[] pdf = baos.toByteArray(); + + return extractTextBinary(pdf); + } + catch (IOException e) + { + throw new PDFDocumentException(202, e); + } + } + + /** + * Extracts the signable text from a binary pdf document. + * + *

+ * The signable text is the text that will be signed or verified afterwards. + *

+ * + * @param pdf + * The pdf document. + * @return Returns the extracted text String. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static String extractTextBinary(final byte[] pdf) throws PDFDocumentException + { + try + { + PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); + PdfDictionary egiz_dict = getEgizDictFromReader(reader); + if (egiz_dict == null) + { + System.out.println("NO Egiz Dict found - whole doc = original doc"); + + int ods = pdf.length; + return retrieveSignableTextFromData(pdf, ods); + } + + String sig_text = extractSignatureTextOnly(egiz_dict); + + int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); + + String raw_text = retrieveSignableTextFromData(pdf, ods); + raw_text += "\n"; + raw_text += sig_text; + + return raw_text; + } + catch (IOException e) + { + throw new PDFDocumentException(202, e); + } + } + + /** + * Retrieves the List of SignatureHolders containing the information of all + * digital signatures of the given document. + * + *

+ * If the List of SignatureHolders is empty, the document is not signed + * anyways. + *

+ * + * @param pdf + * The complete pdf document. + * @return Returns the List of SignatureHolders. + * @throws PDFDocumentException + * @throws SignatureTypesException + * @throws SignatureException + */ + public static List extractSignatureHoldersBinary(final byte[] pdf) throws PDFDocumentException, SignatureTypesException, SignatureException + { + try + { + PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); + List chain = getEgizDictChainFromReader(reader); + + List signatures = new ArrayList(); + Iterator it = chain.iterator(); + while (it.hasNext()) + { + PdfDictionary dict = (PdfDictionary) it.next(); + + int ods = getOriginalDocumentSizeFromEgizDict(dict); + String signature_text = extractSignatureTextOnly(dict); + + SignatureTypes sig_types = SignatureTypes.getInstance(); + List types = sig_types.getSignatureTypeDefinitions(); + SignatureBlock sig_block = new SignatureBlock(types); + boolean could_separate = sig_block.separateBlockFromRawText(signature_text, false); + + if (could_separate) + { + SignatureObject sig_object = sig_block.getSignatureObject(); + + SignatureHolder holder = new BinarySignatureHolder(pdf, ods, sig_object); + signatures.add(holder); + } + } + + return signatures; + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + } + + // /** + // * Signs a document with the given signature table using the Incremental + // * Update method. + // * + // *

+ // * The table containing the signature text will be appended. As specified by + // * the parameters, the signature will be appended to the last page, or a + // plain + // * new page will be created for the signature to hold. + // *

+ // *

+ // * The table will be completely wrapped by an XObject, which will also be + // * indirectly referenced by the Egiz Dictionary. This will ease the + // * verification process. + // *

+ // *

+ // * An Egiz Dictionary will be added to the new document that contains + // * information about the signature. Basically the size of the original + // * document and the reference of the signature table. + // *

+ // * + // * @param original_document + // * The file name of the original document. + // * @param new_document + // * The file name of the new document to be created. + // * @param pdf_table + // * The PdfPTable that contains the signature block. + // * @param pos_x + // * The x position where the table should be inserted. + // * @param pos_y + // * The y position where the table should be inserted (on the last + // * page). If this is negative, a new page will be appended to the + // * document. Then the table will be inserted on that new page using + // * the absolute value of pos_y. Note that pos_y specifies the top + // * line of the table. + // * @throws PresentableException + // * Forwarded exception. + // * + // * @see #writeIncrementalUpdate(byte[], PdfPTable, float, float, boolean) + // */ + // public static void writeIncrementalUpdate(String original_document, + // String new_document, PdfPTable pdf_table, float pos_x, float pos_y, + // int egiz_dict_num_replaces) throws PresentableException + // { + // try + // { + // File original_document_file = new File(original_document); + // FileInputStream fis = new FileInputStream(original_document_file); + // byte[] pdf = new byte[(int) original_document_file.length()]; + // fis.read(pdf); + // fis.close(); + // + // byte[] signed_pdf = writeIncrementalUpdate(pdf, pdf_table, pos_x, pos_y, + // egiz_dict_num_replaces); + // + // File new_document_file = new File(new_document); + // FileOutputStream fos = new FileOutputStream(new_document_file); + // fos.write(signed_pdf); + // fos.close(); + // } + // catch (IOException e) + // { + // throw new PresentableException(e); + // } + // } + + /** + * Signs a document with the given signature table using the Incremental + * Update method. + * + *

+ * The table containing the signature text will be appended. As specified by + * the parameters, the signature will be appended to the last page, or a plain + * new page will be created for the signature to hold. + *

+ *

+ * The table will be completely wrapped by an XObject, which will also be + * indirectly referenced by the Egiz Dictionary. This will ease the + * verification process. + *

+ *

+ * An Egiz Dictionary will be added to the new document that contains + * information about the signature. Basically the size of the original + * document and the reference of the signature table. + *

+ * + * @param original_document + * The original document. + * @param pdf_table + * The PdfPTable that contains the signature block. + * @param pos + * The Position object giving the exact location where to place the + * table. if page is -1, a new page will be appended to the document. + * Then the table will be inserted on that new page using the + * coordinates. + * @return Returns the new document. + * @throws PresentableException + * Forwarded exception. + */ + public static IncrementalUpdateInformation writeIncrementalUpdate( + byte[] original_document, PdfPTable pdf_table, TablePos pos, + List variable_field_definitions, List all_field_definitions) throws PresentableException + { + try + { + IncrementalUpdateInformation iui = new IncrementalUpdateInformation(); + iui.original_document = original_document; + iui.start_index = original_document.length; + + Document.compress = false; + + // System.out.println("wprinz: STAMPING PDF"); + + PdfReader reader = new PdfReader(original_document); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // IMPORTANT: append the new content to the original document using + // incremental updated + // The stamper allows this by setting append = true + PdfStamper stamper = new PdfStamper(reader, baos, '\0', true); + + int pdf_page_num = reader.getNumberOfPages(); + + // int signature_page = -1; + // + // if (pos_y >= 0) + // { + // signature_page = pdf_page_num; + // } + if (pos.page == -1) + { + pos.page = pdf_page_num + 1; + + Rectangle psize = reader.getPageSizeWithRotation(pdf_page_num); + Rectangle rect = new Rectangle(psize); + stamper.insertPage(pos.page, rect); + + // pos_y *= -1; + } + + if (pos.page < 1 || pos.page > stamper.getReader().getNumberOfPages()) + { + throw new PDFDocumentException(224, "The provided pos.page (=" + pos.page + ") is out of range."); + } + PdfContentByte content = stamper.getOverContent(pos.page); + // content = StampContent einer PageStamp. + + // System.out.println("table_width = " + pdf_table.getTotalWidth() + ", + // table_height = " + pdf_table.getTotalHeight()); + + PdfTemplate table_template = content.createTemplate(pdf_table.getTotalWidth(), pdf_table.getTotalHeight()); + + pdf_table.writeSelectedRows(0, -1, 0, pdf_table.getTotalHeight(), table_template); + + // table_template.moveTo(0, 0); + // table_template.lineTo(pdf_table.getTotalWidth(), + // pdf_table.getTotalHeight()); + // table_template.stroke(); + // table_template.moveTo(0, 0); + // table_template.lineTo(100, 100); + // table_template.stroke(); + + // pdf_table.writeSelectedRows(0, -1, SIGNATURE_BORDER / 2, + // table_position, content); + + content.addTemplate(table_template, pos.pos_x, pos.pos_y - pdf_table.getTotalHeight()); + + // For debugging print a 100x100 grid +// { +// Rectangle psize = reader.getPageSizeWithRotation(pos.page); +// float page_width = psize.width(); +// float page_height = psize.height(); +// for (float x = 0; x < page_width; x += 100) +// { +// content.moveTo(x, 0); +// content.lineTo(x, page_height); +// content.stroke(); +// } +// for (float y = 0; y < page_height; y += 100) +// { +// content.moveTo(0, y); +// content.lineTo(page_width, y); +// content.stroke(); +// } +// } + + // content.setLineWidth(10.0f); + // content.moveTo(0, 0); + // content.lineTo(100, 100); + // content.stroke(); + + // PdfIndirectReference page_ref = + // stamper.getWriter().getPageReference(signature_page); + // System.out.println("page_ref = " + page_ref.toString()); + + // PdfObject page_obj = PdfReader.getPdfObject(page_ref); + // System.out.println("page_obj = " + page_obj); + + // PdfDictionary page_dict = (PdfDictionary) page_obj; + // PdfObject resources_obj = page_dict.get(PdfName.RESOURCES); + // System.out.println("resources_obj = " + resources_obj); + // PdfDictionary resources = (PdfDictionary) + // PdfReader.getPdfObject(resources_obj); + // for (Iterator it = resources.getKeys().iterator(); it.hasNext();) + // { + // PdfName key = (PdfName) it.next(); + // PdfObject value = resources.get(key); + // System.out.println(" " + key + " = " + value); + // } + + // add the EGIZ dict: + if (variable_field_definitions != null) + { + createEgizDict(stamper, table_template, iui, variable_field_definitions, all_field_definitions); + } + + stamper.close(); + // System.out.println("wprinz: STAMPING FINISHED"); + + iui.signed_pdf = baos.toByteArray(); + + return iui; + } + catch (IOException e) + { + e.printStackTrace(); + throw new PresentableException(e); + } + catch (DocumentException e) + { + e.printStackTrace(); + throw new PresentableException(e); + } + } + + /** + * Creates the EGIZ Dictionary and adds it to the document. + * + * @param stamper + * The PdfStamper. + * @param table_template + * The Template of the Signature block. + * @param iui + * The IncrementalUpdateInformation. + * @param variable_field_definitions + * The field definitions. + * @throws IOException + * @throws SettingNotFoundException + */ + protected static void createEgizDict(PdfStamper stamper, + PdfTemplate table_template, IncrementalUpdateInformation iui, + List variable_field_definitions, List all_field_definitions) throws IOException, SettingNotFoundException + { + // iui.temp_ir = table_template.getIndirectReference(); + iui.temp_ir_number = table_template.getIndirectReference().getNumber(); + iui.temp_ir_generation = table_template.getIndirectReference().getGeneration(); + + byte[] content_stream = table_template.toPdf(null); + iui.content_stream_length = content_stream.length; + + iui.replaces = determineReplacesInContentStream(content_stream, 0, content_stream.length, variable_field_definitions); + iui.kz_list = determineKZ(content_stream, 0, content_stream.length, all_field_definitions); + + // PdfIndirectReference previous_egiz_dict_ind_ref = + // getEgizDictIndRefFromReader(reader); + + PdfDictionary egiz_dict = new PdfDictionary(EGIZ_DICT_NAME); + egiz_dict.put(EGIZ_XOBJ_NAME, table_template.getIndirectReference()); + egiz_dict.put(EGIZ_ODS_NAME, NUMBER_PLACEHOLDER); + + PdfArray kz_array = new PdfArray(); + for (int i = 0; i < iui.kz_list.size(); i++) + { + kz_array.add(NUMBER_PLACEHOLDER); // start + kz_array.add(NUMBER_PLACEHOLDER); // length + } + egiz_dict.put(EGIZ_KZ_NAME, kz_array); + + int num_replaces = calcNumReps(iui.replaces); + int num_holes = num_replaces + 1 + 1; + // +1 = the /encodings hole + // +1 = the /Cert + int num_byte_ranges = num_holes + 1; + + PdfArray byte_ranges_array = new PdfArray(); + for (int i = 0; i < num_byte_ranges; i++) + { + byte_ranges_array.add(NUMBER_PLACEHOLDER); // start + byte_ranges_array.add(NUMBER_PLACEHOLDER); // length + } + egiz_dict.put(EGIZ_BYTERANGES_NAME, byte_ranges_array); + + PdfArray encodings_array = new PdfArray(); + encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the + // /encodings + encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the /Cert + + // array itself + PdfArray replaces_array = new PdfArray(); + replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the + // /encodings + replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the + // /Cert + + Iterator it = iui.replaces.iterator(); + while (it.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) it.next(); + for (int i = 0; i < ri.replaces.size(); i++) + { + byte[] brev = typeToBrev(ri.sfd.field_name); + String brev_string = new String(brev, "US-ASCII"); + PdfName brev_name = new PdfName(brev_string); + replaces_array.add(brev_name); + + encodings_array.add(new PdfName(new String(ENCODING_WIN, "US-ASCII"))); + } + } + egiz_dict.put(EGIZ_REPLACES_NAME, replaces_array); + + egiz_dict.put(EGIZ_ENCODINGS_NAME, encodings_array); + + PdfArray cert_array = new PdfArray(); + byte[] cert_bytes = new byte[CERTIFICATE_PLACEHOLDER_LENGTH]; + for (int i = 0; i < cert_bytes.length; i++) + { + cert_bytes[i] = 0; + } + PdfString cert_placeholder = new PdfString(cert_bytes); + cert_array.add(cert_placeholder); + egiz_dict.put(EGIZ_CERTIFICATE_NAME, cert_array); + + // if (previous_egiz_dict_ind_ref != null) + // { + // egiz_dict.put(PdfName.PREV, previous_egiz_dict_ind_ref); + // } + // no EGIZ chain + + PdfIndirectObject dict_ref = stamper.getWriter().addToBody(egiz_dict); + // iui.egiz_dict_ir = dict_ref.getIndirectReference(); + iui.egiz_dict_ir_number = dict_ref.getIndirectReference().getNumber(); + iui.egiz_dict_ir_generation = dict_ref.getIndirectReference().getGeneration(); + + PdfIndirectReference root_ref = (PdfIndirectReference) stamper.getReader().getTrailer().get(PdfName.ROOT); + PdfDictionary root = (PdfDictionary) PdfReader.getPdfObject(root_ref); + // root.put(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); + ((PdfStamperImp) stamper.getWriter()).markUsed(root); + + // PdfDictionary extra_cata = stamper.getWriter().getExtraCatalog(); + // extra_cata.put(dict_type, dict_ref.getIndirectReference()); + + ((PdfStamperImp) stamper.getWriter()).setEgizDictTrailerInfo(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); + } + + /** + * Converts a field name (type) to the corresponding BREV. + * + * @param type + * The field name (type). + * @return Returns the corresponding BREV, or BREV_NIL if the type is not + * recognized. + */ + protected static byte[] typeToBrev(String type) + { + if (type.equals(SignatureTypes.SIG_DATE)) + { + return BREV_DAT; + } + if (type.equals(SignatureTypes.SIG_ISSUER)) + { + return BREV_ISS; + } + if (type.equals(SignatureTypes.SIG_VALUE)) + { + return BREV_VAL; + } + if (type.equals(SignatureTypes.SIG_NUMBER)) + { + return BREV_SNR; + } + if (type.equals(SignatureTypes.SIG_ID)) + { + return BREV_SID; + } + + return BREV_NIL; + } + + /** + * Updates the information in the egiz dictionary to reflect the real offsets + * of the byte ranges. + * + *

+ * This replaces the "dummy numbers" in the egiz dictionary with the correct + * values. + *

+ * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void markByteRanges(IncrementalUpdateInformation iui) throws PDFDocumentException + { + try + { + iui.byte_ranges = new ArrayList(); + + int num_digits = Integer.toString(NUMBER_PLACEHOLDER.intValue()).length(); + byte[] signed_pdf = iui.signed_pdf; + + String str = iui.egiz_dict_ir_number + " " + iui.egiz_dict_ir_generation + " obj"; + byte[] obj_bytes = str.getBytes("US-ASCII"); + int obj_index = ByteArrayUtils.lastIndexOf(signed_pdf, obj_bytes); + int obj_start = obj_index + obj_bytes.length; + + String ods_str = "/ODS "; + byte[] ods_bytes = ods_str.getBytes("US-ASCII"); + int ods_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, ods_bytes); + int ods_start = ods_index + ods_bytes.length; + + String kz_str = "/ID["; + byte[] kz_bytes = kz_str.getBytes("US-ASCII"); + int kz_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, kz_bytes); + int kz_start = kz_index + kz_bytes.length; + + String br_str = "/ByteRange["; + byte[] br_bytes = br_str.getBytes("US-ASCII"); + int br_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, br_bytes); + int array_start = br_index + br_bytes.length; + + String enc_str = "/encodings["; + byte[] enc_bytes = enc_str.getBytes("US-ASCII"); + int enc_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, enc_bytes); + int enc_start = enc_index + enc_bytes.length; + + String cert_str = "/Cert[("; + byte[] cert_bytes = cert_str.getBytes("US-ASCII"); + int cert_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, cert_bytes); + int cert_start = cert_index + cert_bytes.length; + + replaceNumber(signed_pdf, ods_start, signed_pdf.length, num_digits); + + // update the Kennzeichnung byte ranges + int cur_pos = kz_start; + for (int i = 0; i < iui.kz_list.size(); i++) + { + StringInfo si = (StringInfo) iui.kz_list.get(i); + + replaceNumber(signed_pdf, cur_pos, si.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, si.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + } + + int num_replaces = calcNumReps(iui.replaces); + int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole + // int num_byte_ranges = num_holes + 1; + + cur_pos = array_start; + int cur_br_start = 0; + + // write the /encodings byte range + { + int enc_length = (1 + 3) * num_holes; + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = enc_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = enc_start + enc_length; + + iui.enc_start = enc_start; + iui.enc_length = enc_length; + } + + // write the /Cert byte range + { + int cert_length = CERTIFICATE_PLACEHOLDER_LENGTH; + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = cert_start - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = cert_start + cert_length; + + iui.cert_start = cert_start; + iui.cert_length = cert_length; + } + + Iterator rit = iui.replaces.iterator(); + while (rit.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) rit.next(); + + // byte [] value_bytes = ri.value.getBytes("ISO-8859-1"); + // int write_pos = 0; + + Iterator sit = ri.replaces.iterator(); + while (sit.hasNext()) + { + StringInfo si = (StringInfo) sit.next(); + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = si.string_start - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = si.string_start + si.string_length; + } + + } + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = signed_pdf.length - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + + } + + /** + * Replaces the certificate placeholder with the certificate from the signed + * Signature Object. + * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void replaceCertificate(IncrementalUpdateInformation iui) throws PDFDocumentException + { + X509Cert cert = iui.signed_signature_object.getX509Cert(); + // X509Certificate certificate = cert.getX509Certificate(); + try + { + byte[] encoded = cert.getCertString().getBytes("US-ASCII"); // certificate.getEncoded(); + byte[] escaped = Placeholder.escapePDFString(encoded); + if (escaped.length > iui.cert_length) + { + throw new PlaceholderException("certificate", escaped.length - iui.cert_length); + } + System.arraycopy(escaped, 0, iui.signed_pdf, iui.cert_start, escaped.length); + } + // catch (CertificateEncodingException e) + // { + // throw new PDFDocumentException(300, e); + // } + catch (UnsupportedEncodingException e) + { + throw new PDFDocumentException(300, e); + } + } + + /** + * Replaces the placeholders with values from the signed SignatureObject. + * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void replacePlaceholders(IncrementalUpdateInformation iui) throws PDFDocumentException + { + final byte[] signed_pdf = iui.signed_pdf; + + // int num_replaces = calcNumReps(iui.replaces); + // int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole; +1 = + // the + // /Cert + + int encoding_entry_index = 0; + {// /encodings itself + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); + encoding_entry_index++; + } + {// /Cert itself + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); + encoding_entry_index++; + } + + for (int i = 0; i < iui.replaces.size(); i++) + { + ReplaceInfo ri = (ReplaceInfo) iui.replaces.get(i); + + try + { + String value = ri.value; + + if (value == null) + { + value = ""; + } + + byte[] encoding = ENCODING_WIN; + byte[] replace_bytes = Placeholder.applyWinAnsiEncoding(value); + + String restored_value = Placeholder.unapplyWinAnsiEncoding(replace_bytes); + if (!value.equals(restored_value)) + { + // debug: + System.out.println("WinAnsiEncoding doesn't fit - using URL instead!"); + // /debug! + + replace_bytes = Placeholder.applyURLEncoding(value); + + encoding = ENCODING_URL; + } + + for (int string_index = 0; string_index < ri.replaces.size(); string_index++) + { + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(encoding, 0, signed_pdf, enc_offset + 1, encoding.length); + encoding_entry_index++; + } + + Placeholder.replacePlaceholderWithTolerance(signed_pdf, ri.replaces, replace_bytes, LINE_BREAK_TOLERANCE); + } + catch (PlaceholderException e) + { + throw new PlaceholderException(ri.sfd.field_name, e.getMissing()); + } + + } + } + + /** + * Calculates the number of actual String replaces from a given ReplaceInfo + * list. + *

+ * This is used to determine the number of actual replaces that has to be + * carried out. Accordingly to this number, entries in the dictionary are + * created. + *

+ * + * @param replaces + * The ReplaceInfo list. + * @return Returns the number of string replaces. + */ + protected static int calcNumReps(List replaces) + { + int number = 0; + Iterator it = replaces.iterator(); + while (it.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) it.next(); + number += ri.replaces.size(); + } + return number; + } + + /** + * Determines the List of ReplaceInfo objects of replaces in the content + * stream regarding the given field definitions. + * + *

+ * This method collects all variable String fields in a content stream and + * orders them according to their start offset. + *

+ * + * @param pdf + * The PDF. + * @param begin + * The start of the content stream. + * @param end + * The end of the content stream. + * @param field_definitions + * The field definitions that are counceled to find out which and + * where varaible strings are. + * @return Returns the list of ReplaceInfo objects specifying the variable + * areas. + */ + protected static List determineReplacesInContentStream(final byte[] pdf, + int begin, int end, List field_definitions) + { + List replaces = new ArrayList(); + try + { + + List strings = Placeholder.parseStrings(pdf, begin, end); + + for (int index = 0; index < field_definitions.size(); index++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); + + if (sfd.placeholder_length > 0) + { + ReplaceInfo ri = new ReplaceInfo(); + ri.sfd = sfd; + ri.replaces = new ArrayList(); + + byte[] caption = sfd.caption.getBytes("ISO-8859-1"); + + int caption_index = findIndex(strings, caption); + int start_index = skipStrings(strings, caption_index, caption); + int next_index = findFirstNotPlaceholder(strings, start_index); + + for (int i = start_index; i < next_index; i++) + { + StringInfo si = (StringInfo) strings.get(i); + ri.replaces.add(si); + } + + replaces.add(ri); + } + } + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + + // sort replaces + Collections.sort(replaces, new Comparator() + { + public int compare(Object arg0, Object arg1) + { + ReplaceInfo ri0 = (ReplaceInfo) arg0; + ReplaceInfo ri1 = (ReplaceInfo) arg1; + int start0 = ((StringInfo) ri0.replaces.get(0)).string_start; + int start1 = ((StringInfo) ri1.replaces.get(0)).string_start; + return start0 - start1; + } + }); + + return replaces; + } + + /** + * Determines the Kennzeichnug in the content stream. + * + * @param pdf + * The PDF. + * @param begin + * The start of the content stream. + * @param end + * The end of the content stream. + * @param field_definitions + * The field definitions. + * @return Returns the List of StringInfo objects representing the KZ field. + * @throws SettingNotFoundException + * F.e. + */ + protected static List determineKZ(final byte[] pdf, int begin, int end, + List field_definitions) throws SettingNotFoundException + { + try + { + List strings = Placeholder.parseStrings(pdf, begin, end); + + for (int index = 0; index < field_definitions.size(); index++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); + + if (sfd.field_name.equals(SignatureTypes.SIG_KZ)) + { + List kz_list = new ArrayList(); + + byte[] caption = sfd.caption.getBytes("ISO-8859-1"); + + int caption_index = findIndex(strings, caption); + int start_index = skipStrings(strings, caption_index, caption); + + int end_index = -1; + for (end_index = start_index; end_index < strings.size(); end_index++) + { + StringInfo si = (StringInfo) strings.get(end_index); + + if (startsWithCaption(si, field_definitions)) + { + break; + } + + kz_list.add(si); + } + + return kz_list; + } + } + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + throw new SettingNotFoundException(102, "Field " + SignatureTypes.SIG_KZ + " not found."); + } + + /** + * Finds the index of the StringInfo within the StringInfo list that has the + * given content (caption). + * + * @param strings + * The list of StringInfos. + * @param caption + * The text to be matched to the strings. + * @return Returns the index of the found string, or -1 if no string matched. + */ + protected static int findIndex(List strings, byte[] caption) + { + for (int i = 0; i < strings.size(); i++) + { + if (isCaption(strings, i, caption)) + { + return i; + } + } + return -1; + } + + protected static boolean isCaption(List strings, int index, byte[] caption) + { + try + { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = index; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + baos.write(si.copyStringBytes()); + } + byte[] str_data = baos.toByteArray(); + byte[] unescaped = Placeholder.unescapePDFString(str_data); + if (ByteArrayUtils.compareByteArrays(unescaped, 0, caption)) + { + return true; + } + else + { + return false; + } + } + catch (IOException e) + { + e.printStackTrace(); + return false; + } + + } + + protected static int skipStrings(List strings, int index, byte[] caption) + { + int length = 0; + for (int i = index; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + length += si.string_length; + + if (length >= caption.length) + { + return i + 1; + } + } + return -1; + } + + /** + * Tells, if the given StringInfo contains only placeholder characters. + * + * @param si + * The StringInfo. + * @param placeholder + * The placeholder character. + * @return Returns true, if the string contains only the given placeholder + * characters, false otherwise. + */ + protected static boolean isPlaceholder(StringInfo si, byte placeholder) + { + byte[] string_bytes = si.copyStringBytes(); + for (int i = 0; i < string_bytes.length; i++) + { + if (string_bytes[i] != placeholder) + { + return false; + } + } + return true; + } + + protected static boolean startsWithCaption(StringInfo si, + List field_definitions) + { + try + { + for (int i = 0; i < field_definitions.size(); i++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(i); + + String caption = sfd.caption; + String str = si.getString("ISO-8859-1"); + + if (caption.startsWith(str)) + { + return true; + } + } + return false; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return false; + } + } + + /** + * Finds the first string after and at the given index not being a placeholder + * string. + * + * @param strings + * The list of StringInfos. + * @param start + * The index where to start the search. + * @return Returns the index of the first not placeholder string, or + * strings.size() if no more non placeholder strings could be found. + */ + protected static int findFirstNotPlaceholder(List strings, int start) + { + for (int i = start; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + if (!isPlaceholder(si, LAYOUT_PLACEHOLDER)) + { + return i; + } + } + return strings.size(); + } + + /** + * Restores the given String to its placeholder. + * + * @param pdf + * The PDF. + * @param si + * The string. + * @param placeholder + * The placeholder the string should be filled with. + */ + public static void restorePlaceholder(final byte[] pdf, StringInfo si, + final byte placeholder) + { + byte[] ph = new byte[si.string_length]; + for (int i = 0; i < ph.length; i++) + { + ph[i] = placeholder; + } + System.arraycopy(ph, 0, pdf, si.string_start, ph.length); + } + + /** + * Reconstructs the replaces from the PDF and forms suitable value strings. + * + * @param pdf + * The PDF. + * @param brevs + * The brevs. + * @param sis + * The StringInfo objects of the strings. + * @return Returns the List of ReplaceInfo objects containing the restored + * values. + * @throws PDFDocumentException + */ + public static List reconstructReplaces(final byte[] pdf, byte[][] brevs, + StringInfo[] sis, byte[][] encodings) throws PDFDocumentException + { + try + { + List replaces = new ArrayList(); + + ReplaceInfo cur_ri = null; + + for (int cur = 0; cur < brevs.length; cur++) + { + if (ByteArrayUtils.compareByteArrays(brevs[cur], 0, BREV_NIL)) + { + continue; + } + + if (cur_ri == null || !ByteArrayUtils.compareByteArrays(cur_ri.brev, 0, brevs[cur])) + { + cur_ri = new ReplaceInfo(); + + cur_ri.replaces = new ArrayList(); + + cur_ri.brev = brevs[cur]; + cur_ri.enc = encodings[cur]; + + replaces.add(cur_ri); + } + + cur_ri.replaces.add(sis[cur]); + } + + // restore value Strings + Iterator rit = replaces.iterator(); + while (rit.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) rit.next(); + ri.value = Placeholder.reconstructStringFromPartition(pdf, ri.replaces, ri.enc); + + // System.out.println(new String(ri.brev, "US-ASCII") + ": " + + // ri.value); + } + + return replaces; + } + catch (IOException e) + { + throw new PDFDocumentException(310, e); + } + + } + + /** + * Reads an unsigned integer number. + * + * @param pdf + * The PDF. + * @param start_index + * The start index of the number. + * @param num_digits + * The number of digits. + * @return Returns the read number. + */ + public static int readNumber(final byte[] pdf, final int start_index, + final int num_digits) + { + try + { + byte[] n_bytes = new byte[num_digits]; + System.arraycopy(pdf, start_index, n_bytes, 0, num_digits); + String n_string = new String(n_bytes, "US-ASCII"); + + int n = Integer.parseInt(n_string); + return n; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return -1; + } + } + + /** + * Replaces a number by the new value. + * + * @param pdf + * The PDF. + * @param start_index + * The start index of the number. + * @param number + * The new number. + * @param num_digits + * The number of digits. + */ + public static void replaceNumber(final byte[] pdf, final int start_index, + final int number, final int num_digits) + { + try + { + if (number < 0) + { + throw new IllegalArgumentException("The given number " + number + " must not be negative."); + } + String number_string = Integer.toString(number); + if (number_string.length() > num_digits) + { + throw new IllegalArgumentException("The given number " + number + " has more than " + num_digits + " digits."); + } + + int leading_zeros = num_digits - number_string.length(); + String zeros_string = ""; + for (int i = 0; i < leading_zeros; i++) + { + zeros_string += "0"; + } + + String total_string = zeros_string + number_string; + byte[] total_bytes = total_string.getBytes("US-ASCII"); + System.arraycopy(total_bytes, 0, pdf, start_index, num_digits); + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + } + + /** + * For debugging purposes. + * + * @param args + * @throws IOException + */ + public static void main(String[] args) throws IOException + { + File signed_doc = new File("C:/wprinz/temp.pdf"); + + PdfReader reader = new PdfReader(new FileInputStream(signed_doc)); + PdfDictionary egiz_dict = getEgizDictFromReader(reader); + if (egiz_dict == null) + { + System.out.println("NO Egiz Dict"); + return; + } + + String sig_text = extractSignatureTextOnly(egiz_dict); + System.out.println("Sig Text:"); + System.out.println(sig_text); + + int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); + System.out.println("Original Document Size = " + ods); + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java new file mode 100644 index 0000000..1f522ff --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: BinarySignatureHolder.java,v 1.1 2006/10/11 07:58:28 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one binary signature block, + * which is the signed/signable pdf and the corresponding SignatureObject. + * + *

+ * The actual signed text is computed by Base64 encoding the binary data when + * first requested. + *

+ *

+ * The corresponding getters can be used to retrieve the signed document (e.g. + * for displaying a preview). + *

+ * + * @author wprinz + */ +public class BinarySignatureHolder implements Serializable, SignatureHolder +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -7208103904479272760L; + + /** + * The whole pdf this holder was extracted from. + */ + private byte[] signed_pdf = null; + + /** + * The number of bytes that give the signed document. + */ + private int signed_pdf_length = -1; + + /** + * The signed text of this object. + * + *

+ * This is the value that will be signed by the Connector. + *

+ */ + private String signed_text = null; + + /** + * The signature object. + */ + private SignatureObject signature_object = null; + + /** + * Constructor that takes the pdf and the SignatureObject as parameters. + * + * @param pdf + * The pdf data. + * @param length + * The length (number of bytes) of the pdf data to be used for being + * converted into "signed text". + * @param so + * The signed signature object. + */ + public BinarySignatureHolder(final byte[] pdf, final int length, SignatureObject so) + { + this.signed_pdf = pdf; + this.signed_pdf_length = length; + this.signature_object = so; + + this.signed_text = null; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignedText() + */ + public String getSignedText() + { + if (this.signed_text == null) + { + computeSignedText(); + } + return this.signed_text; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignatureObject() + */ + public SignatureObject getSignatureObject() + { + return this.signature_object; + } + + /** + * Computes or recomputes the signed text from the underlying binary data. + * + *

+ * This usually encodes the binary data of given length in Base64. + *

+ * + *

+ * This is usually called automatically when the signed text is first + * requested. + *

+ */ + protected void computeSignedText() + { + this.signed_text = BinarySignature.retrieveSignableTextFromData(this.signed_pdf, this.signed_pdf_length); + } + + /** + * Returns the signed_pdf. + * @return Returns the signed_pdf. + */ + public byte[] getSignedPdf() + { + return this.signed_pdf; + } + + /** + * Returns the signed_pdf_length. + * @return Returns the signed_pdf_length. + */ + public int getSignedPdfLength() + { + return this.signed_pdf_length; + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java new file mode 100644 index 0000000..7b71d27 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: EGIZDate.java,v 1.1 2006/10/31 08:08:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a signature date and the signing time as can be found in the + * SIG_DATE field. + * + *

+ * This is used to compare date values of signatures. + *

+ * + * @author wprinz + */ +public class EGIZDate +{ + + /** + * The year. + */ + protected int year; + + /** + * The month. + */ + protected int month; + + /** + * The day. + */ + protected int day; + + /** + * The hour. + */ + protected int hour; + + /** + * The minute. + */ + protected int minute; + + /** + * The second. + */ + protected int second; + + /** + * Constructor that fills the date with values. + * + * @param year + * The year. + * @param month + * The month. + * @param day + * The day. + * @param hour + * The hour. + * @param minute + * The minute. + * @param second + * The second. + */ + public EGIZDate(int year, int month, int day, int hour, int minute, int second) + { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + } + + /** + * Parses the date information from a given date value. + * + *

+ * Usually the date value is one extracted from the value of the SIG_DATE + * field. + *

+ * + * @param date_value + * The date value String. + * @return Returns the parsed EGIZDate. An IllegalArgumentException is thrown + * if the date String has an illegal format. + */ + public static EGIZDate parseFromString(String date_value) + { + // find the according RFC standard and cite it + + // BKU writes a Z after the date for some reason. + Pattern date_pattern = Pattern.compile("^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d[Z]?$"); + Matcher date_matcher = date_pattern.matcher(date_value); + if (!date_matcher.matches()) + { + throw new IllegalArgumentException("The date_value (" + date_value + " has an illegal format."); + } + // for some strange reasons capture groups don't work + + int year = Integer.parseInt(date_value.substring(0, 4)); + int month = Integer.parseInt(date_value.substring(5, 7)); + int day = Integer.parseInt(date_value.substring(8, 10)); + int hour = Integer.parseInt(date_value.substring(11, 13)); + int minute = Integer.parseInt(date_value.substring(14, 16)); + int second = Integer.parseInt(date_value.substring(17, 19)); + + return new EGIZDate(year, month, day, hour, minute, second); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) + { + if (!(obj instanceof EGIZDate)) + { + return false; + } + + return toString().equals(obj.toString()); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "parsed date = " + year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second; + } + + /** + * Converts the date to a long integer. + * + *

+ * An earlier date is lower than a later date. + *

+ *

+ * E.g. a date in 1999 will get a smaller number than a date in 2006. + *

+ * + * @return Returns the compareable long. + */ + protected long toCompareableLong() + { + return +this.year * 12 * 31 * 24 * 60 * 60 + this.month * 31 * 24 * 60 * 60 + this.day * 24 * 60 * 60 + this.hour * 60 * 60 + this.minute * 60 + this.second; + } + + /** + * Compares this EGIZDate to another EXIZDate. + * + * @param other + * The other EGIZDate. + * @return Returns negative if this date is earlier (lower) than the other + * date. Returns 0 if both dates are equal. Returns positive if this + * date is later (higher) than the other date. + */ + public int compareTo(EGIZDate other) + { + long diff = toCompareableLong() - other.toCompareableLong(); + return (int) diff; + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java new file mode 100644 index 0000000..cb983cb --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: IncrementalUpdateInformation.java,v 1.2 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.util.List; + +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * This parameter object contains all useful inforamtion the binary incremental + * update methods need to create and replace a binary singature block. + * + *

+ * This class is basically used to transport information about the document from + * the prepareSign to the finishSign of the Signator. In future, this could be + * extended and encapsulated to task proprietary IUI instances. E.g. a + * BinarySignatorIUI, a TextualSignatorIUI, both implementing the core IUI + * interface, but encapsulating Binary or Textual specialities. + *

+ * + * @author wprinz + */ +public class IncrementalUpdateInformation implements Serializable +{ + /** + * SVUID. + */ + private static final long serialVersionUID = -5904526956127108035L; + + /** + * The original PDF document. + */ + public byte[] original_document = null; + + /** + * The Singature type to be created. + */ + public String signature_type = null; + + /** + * The signed pdf document. + * + *

+ * This is the original document plus the incremental update block. + *

+ */ + public byte[] signed_pdf = null; + + /** + * The start index of this incremental update block. + */ + int start_index = -1; + + /** + * The indirect reference of the egiz dict. + */ + // PdfIndirectReference egiz_dict_ir = null; + public int egiz_dict_ir_number; + + public int egiz_dict_ir_generation; + + /** + * The List of ReplaceInfo objects specifying the byte ranges where the + * variable data has to be fille in. + */ + public List replaces = null; + + /** + * The List of StringInfo objects specifying the byte ranges that should + * be/were signed. + */ + public List byte_ranges = null; + + /** + * The indirect reference of the signature x-object. + */ + // public PdfIndirectReference temp_ir; + public int temp_ir_number; + + public int temp_ir_generation; + + /** + * The start index of the content stream of the signature x-object. + */ + public int content_stream_start = -1; + + /** + * The length of the content stream of the signature x-object. + */ + public int content_stream_length = -1; + + /** + * The document text for signing. + */ + public String document_text; + + /** + * The SignatureObject containing the variable values after the document text + * has been signed. + *

+ * These values have to be filled in. + *

+ */ + public SignatureObject signed_signature_object; + + /** + * The start of the /encodings array. + */ + public int enc_start = -1; + + /** + * The length of the /encodings array. + */ + public int enc_length = -1; + + /** + * The start of the first /Cert + */ + public int cert_start = -1; + + /** + * The length of the /cert placeholder. + */ + public int cert_length = -1; + + /** + * The list of strings of the KZ. + */ + public List kz_list; + + /** + * The table position. + */ + public TablePos pos; + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java new file mode 100644 index 0000000..bed1b65 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java @@ -0,0 +1,539 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: PDFPage.java,v 1.5 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.pdfbox.cos.COSName; +import org.pdfbox.cos.COSStream; +import org.pdfbox.pdmodel.PDPage; +import org.pdfbox.pdmodel.PDResources; +import org.pdfbox.pdmodel.common.PDStream; +import org.pdfbox.pdmodel.graphics.xobject.PDXObject; +import org.pdfbox.pdmodel.graphics.xobject.PDXObjectForm; +import org.pdfbox.util.Matrix; +import org.pdfbox.util.PDFOperator; +import org.pdfbox.util.PDFTextStripper; +import org.pdfbox.util.TextPosition; +import org.pdfbox.util.operator.OperatorProcessor; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; + +/** + * PDFPage is an inner class that is used to calculate the page length of a PDF + * Document page. It extends the PDFTextStripper class and implement one + * interested method: {@link PDFPage#showCharacter(TextPosition)}
+ * This method is called when processing the FileStream. By calling the method + * {@link org.pdfbox.util.PDFStreamEngine#processStream(org.pdfbox.pdmodel.PDPage, org.pdfbox.pdmodel.PDResources, org.pdfbox.cos.COSStream)} + * the implemented method showCharacter is called. + * + * @author wlackner + * @see PDFTextStripper + */ +public class PDFPage extends PDFTextStripper +{ + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFPage.class); + + /** + * The maximum (lowest) y position of a character. + */ + protected float max_character_ypos = Float.NEGATIVE_INFINITY; + + /** + * The maximum (lowest y position of an image. + */ + protected float max_image_ypos = Float.NEGATIVE_INFINITY; + + /** + * The empty constructor. + * + * @throws IOException + */ + public PDFPage() throws IOException + { + super(); + + OperatorProcessor newInvoke = new MyInvoke(); + newInvoke.setContext(this); + operators.put("Do", newInvoke); + } + + // /** + // * You should override this method if you want to perform an action when a + // * string is being shown. + // * + // * @param string The string to display. + // * + // * @throws IOException If there is an error showing the string + // */ + // public void showString( byte[] string ) throws IOException + // { + // float spaceWidth = 0; + // float spacing = 0; + // StringBuffer stringResult = new StringBuffer(string.length); + // + // float characterDisplacement = 0; + // float spaceDisplacement = 0; + // + // PDGraphicsState graphicsState = getGraphicsState(); + // float fontSize = graphicsState.getTextState().getFontSize(); + // float horizontalScaling = + // graphicsState.getTextState().getHorizontalScalingPercent()/100f; + // float rise = graphicsState.getTextState().getRise(); + // final float wordSpacing = graphicsState.getTextState().getWordSpacing(); + // final float characterSpacing = + // graphicsState.getTextState().getCharacterSpacing(); + // float wordSpacingDisplacement = 0; + // + // PDFont font = graphicsState.getTextState().getFont(); + // + // //This will typically be 1000 but in the case of a type3 font + // //this might be a different number + // float glyphSpaceToTextSpaceFactor = 1f/font.getFontMatrix().getValue( 0, 0 + // ); + // Float averageWidth = (Float)fontToAverageWidths.get( font ); + // if( averageWidth == null ) + // { + // averageWidth = new Float( font.getAverageFontWidth() ); + // fontToAverageWidths.put( font, averageWidth ); + // } + // + // Matrix initialMatrix = new Matrix(); + // initialMatrix.setValue(0,0,1); + // initialMatrix.setValue(0,1,0); + // initialMatrix.setValue(0,2,0); + // initialMatrix.setValue(1,0,0); + // initialMatrix.setValue(1,1,1); + // initialMatrix.setValue(1,2,0); + // initialMatrix.setValue(2,0,0); + // initialMatrix.setValue(2,1,rise); + // initialMatrix.setValue(2,2,1); + // + // + // //this + // int codeLength = 1; + // Matrix ctm = graphicsState.getCurrentTransformationMatrix(); + // + // //lets see what the space displacement should be + // spaceDisplacement = (font.getFontWidth( SPACE_BYTES, 0, 1 + // )/glyphSpaceToTextSpaceFactor); + // if( spaceDisplacement == 0 ) + // { + // spaceDisplacement = + // (averageWidth.floatValue()/glyphSpaceToTextSpaceFactor); + // //The average space width appears to be higher than necessary + // //so lets make it a little bit smaller. + // spaceDisplacement *= .80f; + // if( log.isDebugEnabled() ) + // { + // log.debug( "Font: Space From Average=" + spaceDisplacement ); + // } + // } + // int pageRotation = page.findRotation(); + // + // // very strange.... the ctms are multiplied by right, but suddenly the + // textM is multiplied from the left. + // // but: PDF matrices are multiplied from left ==> ctm is wrong + // Matrix trm = initialMatrix.multiply( textMatrix ).multiply( ctm ); + // float x = trm.getValue(2,0); + // float y = trm.getValue(2,1); + // float flipped_y = -y + page.findMediaBox().getHeight(); + // if( pageRotation == 0 ) + // { + // trm.setValue( 2,1, flipped_y ); + // } + // else if( pageRotation == 90 ) + // { + // trm.setValue( 2,0, y ); + // trm.setValue( 2,1, x ); + // } + // else if( pageRotation == 270 ) + // { + // trm.setValue( 2,0, flipped_y ); + // trm.setValue( 2,1, x ); + // } + // for( int i=0; i this.max_character_ypos) + { + this.max_character_ypos = current_y; + //logger_.debug("text.character=" + character + ", y=" + current_y); + // System.err.println(character + "|" + current_y); + } + + // logger_.debug("text.character=" + character + ", y=" + current_y); + // System.err.println(character + "|" + current_y); + } + + // use this funtion getting an unsorted text output + // public void showString(byte[] string) { + // logger_.debug(new String(string)); + // } + + /** + * Returns the calculated page length. + * + * @return the max page length value + */ + public float getMaxPageLength() + { + float max_ypos = Float.NEGATIVE_INFINITY; + + if (this.max_character_ypos > this.max_image_ypos) + { + max_ypos = this.max_character_ypos; + } + else + { + max_ypos = this.max_image_ypos; + } + + return max_ypos; + } + + public class MyInvoke extends OperatorProcessor + { + + public void process(PDFOperator operator, List arguments) throws IOException + { + COSName name = (COSName) arguments.get(0); + logger_.debug(""); + + // PDResources res = context.getResources(); + + Map xobjects = context.getXObjects(); + PDXObject xobject = (PDXObject) xobjects.get(name.getName()); + + PDStream stream = xobject.getPDStream(); + COSStream cos_stream = stream.getStream(); + + COSName subtype = (COSName) cos_stream.getDictionaryObject(COSName.SUBTYPE); + if (subtype.equals(COSName.IMAGE)) + { + logger_.debug("XObject Image"); + + Matrix ctm = context.getGraphicsState().getCurrentTransformationMatrix(); + logger_.debug("ctm = " + ctm); + + Pos [] coordinates = new Pos [] { + new Pos(0, 0, 1), + new Pos(1, 0, 1), + new Pos(0, 1, 1), + new Pos(1, 1, 1) }; + + Pos [] transformed_coordinates = transtormCoordinates(coordinates, ctm); + + float actual_lowest_point = Float.NaN; + + int pageRotation = page.findRotation(); + logger_.debug("PageRotation = " + pageRotation); + if (pageRotation == 0) + { + float min_y = findMinY(transformed_coordinates); + logger_.debug("min_y = " + min_y); + float page_height = page.findMediaBox().getHeight(); + logger_.debug("page_height = " + page_height); + + actual_lowest_point = page_height - min_y; + } + if (pageRotation == 90) + { + float max_x = findMaxX(transformed_coordinates); + logger_.debug("max_x = " + max_x); +// float page_width = page.findMediaBox().getWidth(); +// logger_.debug("page_width = " + page_width); + + actual_lowest_point = max_x; + } + if (pageRotation == 180) + { + float min_y = findMinY(transformed_coordinates); + logger_.debug("min_y = " + min_y); + actual_lowest_point = min_y; + } + if (pageRotation == 270) + { + float min_x = findMinX(transformed_coordinates); + logger_.debug("min_x = " + min_x); +// float page_width = page.findMediaBox().getWidth(); +// logger_.debug("page_width = " + page_width); + + actual_lowest_point = min_x; + } + + + logger_.debug("actual_lowest_point = " + actual_lowest_point); + + if (actual_lowest_point > PDFPage.this.max_image_ypos) + { + PDFPage.this.max_image_ypos = actual_lowest_point; + } + + return; + } + + if (xobject instanceof PDXObjectForm) + { + PDXObjectForm form = (PDXObjectForm) xobject; + COSStream invoke = (COSStream) form.getCOSObject(); + PDResources pdResources = form.getResources(); + PDPage page = context.getCurrentPage(); + if (pdResources == null) + { + pdResources = page.findResources(); + } + + getContext().processSubStream(page, pdResources, invoke); + } + } + } + + public static Pos [] transtormCoordinates (Pos [] coordinates, Matrix m) + { + Pos [] transformed = new Pos [coordinates.length]; + for (int i = 0; i < coordinates.length; i++) + { + transformed[i] = transtormCoordinate(coordinates[i], m); + } + return transformed; + } + + public static Pos transtormCoordinate (Pos pos, Matrix m) + { + Pos transformed = new Pos(); + transformed.x = pos.x * m.getValue(0, 0) + pos.y * m.getValue(1, 0) + pos.z * m.getValue(2, 0); + transformed.y = pos.x * m.getValue(0, 1) + pos.y * m.getValue(1, 1) + pos.z * m.getValue(2, 1); + transformed.z = pos.x * m.getValue(0, 2) + pos.y * m.getValue(1, 2) + pos.z * m.getValue(2, 2); + + logger_.debug(" transformed " + pos + " --> " + transformed); + return transformed; + } + + public static float findMinY (Pos [] coordinates) + { + float min = Float.POSITIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].y < min) + { + min = coordinates[i].y; + } + } + return min; + } + public static float findMaxX (Pos [] coordinates) + { + float max = Float.NEGATIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].x > max) + { + max = coordinates[i].x; + } + } + return max; + } + public static float findMinX (Pos [] coordinates) + { + float min = Float.POSITIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].x < min) + { + min = coordinates[i].x; + } + } + return min; + } + +} \ No newline at end of file diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java new file mode 100644 index 0000000..453103b --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java @@ -0,0 +1,170 @@ +/* + * + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE + * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, + * OR NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES + * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING + * THIS SOFTWARE OR ITS DERIVATIVES. + * + * $Id: PDFSignatureCreation.java,v 1.6 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * This class provides wrapper methods to get an access to abstract PDF documents (PDFSignator). + * There exists many open source libraries and commercial libraries that can implement the abstract + * interface.
+ * This class is to load the corresponding implementation of an abstract PDFSignator class. Therefor + * it seams to be a factory. The factory settings are read from the configuration file calling the + * SettingsReader. + * + * @author wlackner + * @see at.knowcenter.wag.egov.egiz.cfg.SettingsReader + */ +public class PDFSignatureCreation { + /** + * The abstract signature object + */ + private SignatureObject sigObject_ = null; + /** + * The abstract pdf siganture object + */ + private PDFSignatureObject pdfSigObject_ = null; + /** + * The SettingsReader instance + */ + private SettingsReader settings_ = null; + /** + * The factory class prefix + */ + private final static String CLASS_PREFIX = ".PDFSignatureObject"; + /** + * The factory class prefix of the default library + */ + protected final static String DEFAULT_LIBRARY = "IText"; + /** + * The settings key defined in the settings file + * + * @see SettingsReader + */ + protected final static String SETTINGS_KEY = "pdf.signature.library"; + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFSignatureCreation.class); + + /** + * Load the configuration settings. Load the corresponding class implementation for the abstract + * PDFSignature class. Init with a signature object. + * + * @param sigObject the native signature object + * @throws PDFDocumentException ErrorCode:101 + */ + public PDFSignatureCreation(SignatureObject sigObject) throws PDFDocumentException { + try { + loadSettings(); + } catch (PDFDocumentException e) { + e.setErrorCode(101); + throw e; + } + sigObject_ = sigObject; + } + + /** + * Load the factory implementation. This method trys to load the configured PDF library. + * + * @throws PDFDocumentException + */ + private PDFSignatureObject createPDFSignatureObject() throws PDFDocumentException { + PDFSignatureObject pdf_sig_object = null; + String class_name = this.getClass().getPackage().getName() + getClassName(); + Class pdf_sig_obj_class = null; + try { + pdf_sig_obj_class = Class.forName(class_name); + } catch (ClassNotFoundException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Class not found:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } + try { + pdf_sig_object = (PDFSignatureObject) pdf_sig_obj_class.newInstance(); + } catch (InstantiationException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Can not instantiate:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } catch (IllegalAccessException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Can not access:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } + return pdf_sig_object; + } + + /** + * load the class settings + * + * @throws PDFDocumentException + * @see SettingsReader + */ + private void loadSettings() throws PDFDocumentException { + if (settings_ == null) { + try { + settings_ = SettingsReader.getInstance(); + } catch (SettingsException e) { + String log_message = "Can not load pdf signature settings. Cause:\n" + e.getMessage(); + logger_.error(log_message); + throw new PDFDocumentException(101, log_message, e); + } + } + } + + /** + * Read the class postfix from the configuration file + * + * @return the full qualified class name + */ + private String getClassName() { + String extract_class = settings_.getSetting(SETTINGS_KEY, DEFAULT_LIBRARY); + return CLASS_PREFIX + extract_class; + } + + /** + * Creates a new pdf signature object using the configured pdf library. + * + * @return a new pdf signature object + * @throws PDFDocumentException ErrorCode:203 + */ + public PDFSignatureObject getPDFSignatureObject() throws PDFDocumentException { + if (pdfSigObject_ == null) { + try { + pdfSigObject_ = createPDFSignatureObject(); + } catch (PDFDocumentException e) { + e.setErrorCode(203); + throw e; + } + pdfSigObject_.setSignatorObject(sigObject_); + } + return pdfSigObject_; + } +} \ No newline at end of file diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java new file mode 100644 index 0000000..3584820 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE + * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, + * OR NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES + * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING + * THIS SOFTWARE OR ITS DERIVATIVES. + * + * $Id: PDFSignatureObject.java,v 1.3 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Defines an interface to get access to PDF documents. There exists many open source libraries and + * commercial libraries. + * + * @author wlackner + */ +public interface PDFSignatureObject { + public void setSignatorObject(SignatureObject signatorObject); + + /** + * Converts the current abstract signature object in a pdf signature object implementation + * + * @return the converted pdf signature object + * @throws PDFDocumentException + */ + public Object getSignatureObject() throws PDFDocumentException; + + /** + * Converts a abstract signature object in a pdf signature object implementation + * + * @param signatorObject the abstract signatorObject to convert + * @return the converted pdf signature object + * @throws PDFDocumentException + */ + public Object getSignatureObject(SignatureObject signatorObject) throws PDFDocumentException; +} \ No newline at end of file diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java new file mode 100644 index 0000000..56b5f86 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: PDFSignatureObjectIText.java,v 1.5 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.ErrorCodeException; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.table.Entry; +import at.knowcenter.wag.egov.egiz.table.Style; +import at.knowcenter.wag.egov.egiz.table.Table; + +import com.lowagie.text.BadElementException; +import com.lowagie.text.Element; +import com.lowagie.text.Font; +import com.lowagie.text.Image; +import com.lowagie.text.Phrase; +import com.lowagie.text.pdf.PdfPCell; +import com.lowagie.text.pdf.PdfPTable; + +/** + * This class is the IText implementation of the PDFSignatureObject interface. + * The class takes an abstract definition of a signature object and convert them + * into a pdf table that is used to sign a pdf document. + * + * @author wlackner + * @see at.knowcenter.wag.egov.egiz.sig.SignatureObject + * @see at.knowcenter.wag.egov.egiz.table.Table + * @see at.knowcenter.wag.egov.egiz.table.Entry + * @see at.knowcenter.wag.egov.egiz.table.Style + * @see com.lowagie.text.pdf.PdfPTable + * @see at.knowcenter.wag.egov.egiz.cfg.SettingsReader + */ +public class PDFSignatureObjectIText implements PDFSignatureObject +{ + + /** + * The default font definition + */ + private static Font DEFAULT_FONT = new Font(Font.HELVETICA, 8, Font.NORMAL); + + /** + * The abstract signature object + */ + private SignatureObject sigObject_ = null; + + /** + * The IText pdf table object + */ + private PdfPTable pdfSigObject_ = null; + + /** + * The SettingsReader instance + */ + private SettingsReader settings_ = null; + + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFSignatureObjectIText.class); + + /** + * Map the style align definitions to IText's align statements + */ + private static HashMap alignMap_ = new HashMap(); + + /** + * Map the font definitions to IText's font statements + */ + private static HashMap fontMap_ = new HashMap(); + + /** + * The empty constructor. It loads the ui definitions from signature tables + * and init the align map. + * + * @throws PDFDocumentException + */ + public PDFSignatureObjectIText() throws PDFDocumentException + { + loadSettings(); + initStyleMaps(); + } + + /** + * load the class settings + * + * @throws PDFDocumentException + * @see SettingsReader + */ + private void loadSettings() throws PDFDocumentException + { + if (settings_ == null) + { + try + { + settings_ = SettingsReader.getInstance(); + } + catch (SettingsException e) + { + String log_message = "Can not load pdf signature settings. Cause:\n" + e.getMessage(); + logger_.error(log_message); + throw new PDFDocumentException(101, log_message, e); + } + } + } + + /** + * This method initialize the style maps. It maps the style style definitions + * to IText styles. + */ + private void initStyleMaps() + { + alignMap_.put(Style.TOP, new Integer(Element.ALIGN_TOP)); + alignMap_.put(Style.MIDDLE, new Integer(Element.ALIGN_MIDDLE)); + alignMap_.put(Style.BOTTOM, new Integer(Element.ALIGN_BOTTOM)); + alignMap_.put(Style.LEFT, new Integer(Element.ALIGN_LEFT)); + alignMap_.put(Style.CENTER, new Integer(Element.ALIGN_CENTER)); + alignMap_.put(Style.RIGHT, new Integer(Element.ALIGN_RIGHT)); + + fontMap_.put(Style.HELVETICA, new Integer(Font.HELVETICA)); + fontMap_.put(Style.TIMES_ROMAN, new Integer(Font.TIMES_ROMAN)); + fontMap_.put(Style.COURIER, new Integer(Font.COURIER)); + fontMap_.put(Style.NORMAL, new Integer(Font.NORMAL)); + fontMap_.put(Style.BOLD, new Integer(Font.BOLD)); + fontMap_.put(Style.ITALIC, new Integer(Font.ITALIC)); + fontMap_.put(Style.BOLDITALIC, new Integer(Font.BOLDITALIC)); + fontMap_.put(Style.UNDERLINE, new Integer(Font.UNDERLINE)); + fontMap_.put(Style.STRIKETHRU, new Integer(Font.STRIKETHRU)); + } + + /** + * Set the abstract signature definition. + * + * @param signatorObject + * the abstract signator object + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#setSignatorObject(at.knowcenter.wag.egov.egiz.sig.SignatureObject) + */ + public void setSignatorObject(SignatureObject signatorObject) + { + sigObject_ = signatorObject; + } + + /** + * This method maps the table cell definitions to the pdfCell element. + * + * @param pdfCell + * the pdf cell to be styled + * @param cellStyle + * the abstract style definition + * @see com.lowagie.text.pdf.PdfPCell + * @see at.knowcenter.wag.egov.egiz.table.Style + */ + private void setCellStyle(PdfPCell pdfCell, Style cellStyle) + { + if (cellStyle != null) + { + if (cellStyle.getBgColor() != null) + { + pdfCell.setBackgroundColor(cellStyle.getBgColor()); + } + pdfCell.setPadding(cellStyle.getPadding()); + if (cellStyle.getBorder() > 0) + { + pdfCell.setBorderWidth(cellStyle.getBorder()); + } + else + { + pdfCell.setBorder(0); + } + if (cellStyle.getVAlign() != null) + { + int align = ((Integer) alignMap_.get(cellStyle.getVAlign())).intValue(); + pdfCell.setVerticalAlignment(align); + } + if (cellStyle.getHAlign() != null) + { + int align = ((Integer) alignMap_.get(cellStyle.getHAlign())).intValue(); + pdfCell.setHorizontalAlignment(align); + } + } + } + + /** + * This method maps the cell font definition to the iText Font Object + * + * @param fontString + * @return the corresponding iText Font Object + * @see com.lowagie.text.Font + */ + private Font getCellFont(String fontString) + { + Font font = DEFAULT_FONT; + if (fontString == null) + { + return font; + } + Object cache_font = fontMap_.get(fontString); + if (cache_font != null) + { + return (Font) cache_font; + } + String[] font_arr = fontString.split(","); + if (font_arr.length != 3) + { + return font; + } + Object font_face = fontMap_.get(font_arr[0]); + if (font_face == null) + { + return font; + } + Object font_weight = fontMap_.get(font_arr[2]); + if (font_weight == null) + { + return font; + } + int face = ((Integer) font_face).intValue(); + float height = Float.parseFloat(font_arr[1]); + int weight = ((Integer) font_weight).intValue(); + + font = new Font(face, height, weight); + fontMap_.put(fontString, font); + return font; + } + + /** + * This method visualize an abstract table cell into a corresponding pdf table + * cell. The new pdf table cell is redered and get the style information from + * the abstract cell. Following types can be rendered: + *
    + *
  • text statements
  • + *
  • images
  • + *
  • tables
  • + *
+ * + * @param abstractCell + * the abstract cell definition + * @return the new redererd pdf table cell + * @throws PDFDocumentException + * ErrorCode:220, 221, 222 + * @see com.lowagie.text.pdf.PdfPCell + * @see at.knowcenter.wag.egov.egiz.table.Entry + */ + private PdfPCell renderCell(Entry abstractCell) throws PDFDocumentException + { + PdfPCell pdf_cell = null; + Style cell_style = abstractCell.getStyle(); + switch (abstractCell.getType()) + { + case Entry.TYPE_CAPTION: + case Entry.TYPE_VALUE: + String text = (String) abstractCell.getValue(); + if (text == null) + { + text = ""; + } + String font_string = cell_style.getFont(); + if (abstractCell.getType() == Entry.TYPE_VALUE && cell_style.getValueFont() != null) + { + font_string = cell_style.getValueFont(); + } + Font cell_font = getCellFont(font_string); + Phrase text_phrase = new Phrase(text, cell_font); + pdf_cell = new PdfPCell(text_phrase); + setCellStyle(pdf_cell, cell_style); + break; + case Entry.TYPE_IMAGE: + try + { + String img_ref = (String) abstractCell.getValue(); + String img_location = SettingsReader.relocateFile(img_ref); + File img_file = new File (img_location); + if (!img_file.exists()) + { + logger_.debug("Image file \"" + img_file.getCanonicalPath() + "\" doesn't exist."); + throw new PDFDocumentException(220, "Image file \"" + img_file.getCanonicalPath() + "\" doesn't exist."); + } + Image image = Image.getInstance(img_location); + pdf_cell = new PdfPCell(image, true); + setCellStyle(pdf_cell, cell_style); + } + catch (BadElementException e) + { + if (logger_.isDebugEnabled()) + { + if (logger_.isDebugEnabled()) + { + e.printStackTrace(); + } + } + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("BadElementException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(220, "PDF table can not created"); + throw pde; + } + catch (MalformedURLException e) + { + if (logger_.isDebugEnabled()) + { + e.printStackTrace(); + } + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("MalformedURLException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(221, "PDF table can not created"); + throw pde; + } + catch (IOException e) + { + if (logger_.isDebugEnabled()) + { + e.printStackTrace(); + } + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("Error Code:222 " + ErrorCodeException.getErrorCodeMessage(222) + ". IOException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(222, "PDF table can not created: Image can not loaded"); + throw pde; + } + break; + case Entry.TYPE_TABLE: + Table table = (Table) abstractCell.getValue(); + // inherit the style from the parent table + Style inherit_style = Style.doInherit(table.getStyle(), cell_style); + table.setStyle(inherit_style); + PdfPTable pdf_table = renderTable(table); + pdf_cell = new PdfPCell(pdf_table); + break; + } + return pdf_cell; + } + + /** + * This method visualize an abstract table into a corresponding pdf table. The + * new pdf table is redered and get the style information from the abstract + * cell. + * + * @param abstractTable + * the abstract table definition + * @return the new redererd pdf table cell + * @throws PDFDocumentException + * ErrorCode:220, 221, 222, 223 + * @see com.lowagie.text.pdf.PdfPTable + * @see at.knowcenter.wag.egov.egiz.table.Table + */ + private PdfPTable renderTable(Table abstractTable) throws PDFDocumentException + { + if (abstractTable == null) + { + PDFDocumentException pde = new PDFDocumentException(223, "Table is not defined."); + throw pde; + } + PdfPTable pdf_table = null; + float[] cols = abstractTable.getColsRelativeWith(); + int max_cols = abstractTable.getMaxCols(); + if (cols == null) + { + cols = new float[max_cols]; + // set the column ratio for all columns to 1 + for (int cols_idx = 0; cols_idx < cols.length; cols_idx++) + { + cols[cols_idx] = 1; + } + } + pdf_table = new PdfPTable(cols); + pdf_table.setWidthPercentage(abstractTable.getWidth()); + Style table_style = abstractTable.getStyle(); + setCellStyle(pdf_table.getDefaultCell(), table_style); + + ArrayList rows = abstractTable.getRows(); + for (int row_idx = 0; row_idx < rows.size(); row_idx++) + { + ArrayList row = (ArrayList) rows.get(row_idx); + // logger_.debug("## Row:" + row_idx + " ## of table:" + + // abstractTable.getName()); + for (int entry_idx = 0; entry_idx < row.size(); entry_idx++) + { + Entry cell = (Entry) row.get(entry_idx); + Style inherit_style = Style.doInherit(cell.getStyle(), table_style); + cell.setStyle(inherit_style); + // logger_.debug(cell.toString()); + PdfPCell pdf_cell = renderCell(cell); + if (cell.getColSpan() > 1) + { + pdf_cell.setColspan(cell.getColSpan()); + } + if (cell.isNoWrap()) + { + pdf_cell.setNoWrap(true); + } + // System.err.println("valign:" + pdf_cell.getVerticalAlignment() + " + // halign:" + + // pdf_cell.getHorizontalAlignment()); + pdf_table.addCell(pdf_cell); + } + } + // logger_.debug("render table:" + abstractTable.getName()); + return pdf_table; + } + + /** + * This method creates the pdf table object. It takes the abstract table + * definition from the signature object and render the abstract table. + * + * @param sigObject + * the signature object, the base for the abstract table definition + * @return R + * @throws PDFDocumentException + * ErrorCode:220, 221, 222, 223 + */ + private PdfPTable createPDFSignatureObject(SignatureObject sigObject) throws PDFDocumentException + { + Table table = sigObject.getAbstractTable(); + PdfPTable pdf_table = renderTable(table); + return pdf_table; + } + + /* + * This method search for the table definitions in the settings file an init + * @param sigObject + */ + /* + * private void initTableSettings(SignatureObject sigObject) { String sig_type = + * sigObject.getSignationType(); String table_key = SignatureObject.SIG_OBJ + + * sig_type + ".table."; ArrayList main_rows = settings_.getKeys(table_key + + * "main"); } + */ + + /** + * Converts the current abstract signature object in a pdf signature object + * implementation + * + * @return the converted pdf signature object + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#getSignatureObject() + */ + public Object getSignatureObject() throws PDFDocumentException + { + if (pdfSigObject_ == null) + { + pdfSigObject_ = (PdfPTable) getSignatureObject(sigObject_); + } + return pdfSigObject_; + } + + /** + * Converts a abstract signature object in a pdf signature object + * implementation + * + * @param sigObject + * the abstract signatorObject to convert + * @return the converted pdf signature object + * @throws PDFDocumentException + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#getSignatureObject(at.knowcenter.wag.egov.egiz.sig.SignatureObject) + */ + public Object getSignatureObject(SignatureObject sigObject) throws PDFDocumentException + { + // initTableSettings(sigObject); + return createPDFSignatureObject(sigObject); + } +} \ No newline at end of file diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java new file mode 100644 index 0000000..8fa3b35 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: PDFUtilities.java,v 1.3 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.pdfbox.pdfparser.PDFParser; +import org.pdfbox.pdmodel.PDDocument; +import org.pdfbox.pdmodel.PDPage; + +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; + +import com.lowagie.text.DocumentException; + +/** + * Contains useful helpers for accessing PDF documents. + * + * @author wprinz + */ +public abstract class PDFUtilities +{ + public static float calculateLastPageLength(final byte[] pdf) throws PDFDocumentException + { + try + { + ByteArrayInputStream original_bais = new ByteArrayInputStream(pdf); + byte [] normalized_pdf = TextualSignature.normalizePDF(original_bais); + + ByteArrayInputStream bais = new ByteArrayInputStream(normalized_pdf); + + PDFParser parser = new PDFParser(bais); + File temporary_dir = SettingsReader.getTemporaryDirectory(); + parser.setTempDirectory(temporary_dir); + parser.parse(); + + PDDocument pdfDocument_ = parser.getPDDocument(); + float last_page_length = calculateLastPageLength(pdfDocument_); + pdfDocument_.close(); + + return last_page_length; + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + catch (DocumentException e) + { + throw new PDFDocumentException(201, e); + } + } + + public static float calculateLastPageLength(PDDocument document) throws IOException + { + int last_page_id = document.getNumberOfPages(); + List allPages = document.getDocumentCatalog().getAllPages(); + PDPage last_page = (PDPage) allPages.get(last_page_id - 1); + + return calculatePageLength(last_page); + } + + public static float calculatePageLength(PDPage page) throws IOException + { + // logger_.debug("Last Page id:" + last_page_id); + // PDPage last_page = (PDPage) allPages.get(0); + PDFPage my_page = new PDFPage(); + my_page.processStream(page, page.findResources(), page.getContents().getStream()); + return my_page.getMaxPageLength(); + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java new file mode 100644 index 0000000..b9c3306 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java @@ -0,0 +1,552 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: Placeholder.java,v 1.5 2006/10/31 08:17:50 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.codec.net.URLCodec; +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; +import at.knowcenter.wag.exactparser.ByteArrayUtils; + +/** + * Helper class that provides functionality for dealing with placeholders and + * replacements in pdf. + * + * @author wprinz + */ +public abstract class Placeholder +{ + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(Placeholder.class); + + /** + * Escapes the String to be a suitable Literal String.. + * + * @param data + * The String to be escaped. + * @return Returns the escaped PDF String. + */ + public static byte[] escapePDFString(byte[] data) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) + { + byte[] escaped_bytes = escapeByte(data[i]); + baos.write(escaped_bytes); + } + return baos.toByteArray(); + } + catch (IOException e) + { + e.printStackTrace(); + return null; + } + } + + /** + * Unescapes the PDF String. + * + * @param data + * The escaped String. + * @return Returns the unescaped String. + */ + public static byte[] unescapePDFString(byte[] data) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) + { + if (data[i] == '\\' && data[i + 1] == '\\') + { + baos.write('\\'); + i++; + continue; + } + if (data[i] == '\\' && data[i + 1] == '(') + + { + baos.write('('); + i++; + continue; + } + if (data[i] == '\\' && data[i + 1] == ')') + { + baos.write(')'); + i++; + continue; + } + baos.write(data[i]); + } + return baos.toByteArray(); + } + + /** + * Reconstructs the string from a partition of placeholders. + * + * @param pdf + * The PDF to read the string from. + * @param sis + * The list of StringInfo objects that specify the bytes of the + * string in the pdf. + * @return Returns the extracted and reconverted string. + * @throws IOException + * Forwarded exception. + */ + public static String reconstructStringFromPartition(byte[] pdf, List sis, + byte[] enc) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + Iterator it = sis.iterator(); + while (it.hasNext()) + { + StringInfo si = (StringInfo) it.next(); + + for (int i = si.string_start; i < si.string_start + si.string_length; i++) + { + if (pdf[i] != 0) + { + baos.write(pdf[i]); + } + } + } + + baos.close(); + byte[] bytes = baos.toByteArray(); + + byte[] unescaped_bytes = unescapePDFString(bytes); + + if (!ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_WIN) && !ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) + { + String enc_str = new String(enc, "US-ASCII"); + logger_.warn("The encoding " + enc_str + " is not known by this application - trying to proceed anyways."); + } + + String text = new String(unescaped_bytes, "windows-1252"); + + String str = text; + if (ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) + { + str = unapplyURLEncoding(str); + } + + return str; + } + + /** + * Prepares the given String to a byte array that can be substituted into the + * placeholder. + * + * @param text + * The text to be prepared for substitution. + * @return Returns the prepared byte array. + */ + public static byte[] applyWinAnsiEncoding(String text) + { + // text = text.replace("\\", "\\\\"); + // text = text.replace("(", "\\("); + // text = text.replace(")", "\\)"); + + byte[] replace_bytes; + try + { + replace_bytes = text.getBytes("windows-1252");// CP1252 = WinAnsiEncoding + + // test the opposite way: + // String restored_string = new String (replace_bytes, "windows-1252"); + // if (!restored_string.equals(text)) + // { + // String url_encoded = URLEncoder.encode(text); + // replace_bytes = url_encoded.getBytes("windows-1252"); + // } + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return null; + } + return replace_bytes; + } + + /** + * Unapplies the WinAnsi encoding. + * + * @param replace_bytes + * The bytes. + * @return Returns the decoded String. + */ + public static String unapplyWinAnsiEncoding(byte[] replace_bytes) + { + try + { + String text = new String(replace_bytes, "windows-1252"); + + return text; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return null; + } + + } + + /** + * Applies the URL encoding to the text. + * + * @param text + * The text + * @return Returns the URL and WinAnsi encoded text. + */ + public static byte[] applyURLEncoding(String text) + { + URLCodec utf8_url_codec = new URLCodec("UTF-8"); + String url_encoded = null; + try + { + url_encoded = utf8_url_codec.encode(text, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("Couldn't url encode : " + text, e); + } + // String url_encoded = URLEncoder.encode(text); + return applyWinAnsiEncoding(url_encoded); + } + + /** + * Unapplies the WinAnsi and URL encoding. + * + * @param winansi_str + * The Winansi and URL text. + * @return Returns the decoded text. + */ + public static String unapplyURLEncoding(String winansi_str) + { + URLCodec utf8_url_codec = new URLCodec("UTF-8"); + String url_decoded = null; + try + { + url_decoded = utf8_url_codec.decode(winansi_str, "UTF-8"); + } + catch (Exception e) + { + throw new RuntimeException("Couldn't url decode : " + winansi_str, e); + } + // String url_decoded = URLDecoder.decode(winansi_str); + return url_decoded; + } + + /** + * Restores the String from a previously prepared byte array. + * + * @param pdf_string + * The byte array. + * @return Returns the unprepared String. + */ + public static String unprepareAndUnescapeString(byte[] pdf_string) + { + try + { + String text = new String(pdf_string, "windows-1252"); + + // This makes problems when "+" appears. + // if (isURLEncoded(text)) + // { + // text = URLDecoder.decode(text); + // } + + text = text.replace("\\)", ")"); + text = text.replace("\\(", "("); + text = text.replace("\\\\", "\\"); + + return text; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return null; + } + } + + /** + * Checks the presence of typical URL encoded characters to tell if the string + * is URL encoded. + * + *

+ * This heuristic checks if there are any non URL encoded characters in the + * String, like ASCII control characters, which aren't allowed in the + * URLEncoding characterset. + *

+ * + * @param text + * The text under suspicion. + * @return Returns true if the String is URL encoded, false otherwise. + */ + protected static boolean isURLEncoded(String text) + { + if (text.indexOf(' ') >= 0) + { + return false; + } + for (int i = 0; i < text.length(); i++) + { + char c = text.charAt(i); + if (0x00 <= c && c <= 0x1f) + { + return false; + } + if (c == 0x7F) + { + return false; + } + if (0x80 <= c) + { + return false; + } + } + return true; + } + + /** + * Tells, if a break can occur behind the given character. + * + * @param character + * The character. + * @return Returns true, if a break may occur behind the character, false + * otherwise. + */ + protected static boolean canBreakAfter(byte character) + { + return (character == ' ' || character == ',' || character == ';'); + } + + /** + * Scans the given PDF content stream for literal PDF strings. + * + * @param pdf + * The PDF. + * @param stream_start + * The start of the content stream to be scanned. + * @param stream_next + * The end of the content stream. + * @return Returns a list of StringInfo objects specifying the strings that + * could be found. + */ + public static List parseStrings(byte[] pdf, int stream_start, int stream_next) + { + List strings = new ArrayList(); + StringInfo cur_string = null; + for (int i = stream_start; i < stream_next; i++) + { + byte cur_byte = pdf[i]; + + if (cur_byte == '(' && pdf[i - 1] != '\\') + { + cur_string = new StringInfo(); + cur_string.pdf = pdf; + cur_string.string_start = i + 1; + cur_string.string_length = -1; + // logger_.debug("String start = " + cur_string.string_start); + continue; + } + if (cur_byte == ')' && pdf[i - 1] != '\\') + { + cur_string.string_length = i - cur_string.string_start; + // logger_.debug("String length = " + cur_string.string_length); + strings.add(cur_string); + + cur_string = null; + continue; + } + } + + return strings; + } + + /** + * Escapes the data byte if necessary. + * + *

+ * Before bytes can be written into the pdf Strings, they have to be escaped. + * Special care has to be taken that escaped sequences are not split due to + * line breaks. This could have fatal consequences and usually renders the + * whole document invalid. + *

+ * + * @param data + * The data byte to be escaped. + * @return Returns a new byte array escaping the data byte. If the byte needs + * not to be escaped, this new array will contain only the original + * data byte. + */ + public static byte[] escapeByte(byte data) + { + if (data == '\\') + { + return new byte[] { '\\', '\\' }; + } + if (data == '(') + { + return new byte[] { '\\', '(' }; + } + if (data == ')') + { + return new byte[] { '\\', ')' }; + } + return new byte[] { data }; + } + + /** + * Replaces the placeholder with the given String breaking lines with a given + * tolerance. + * + * @param pdf + * The PDF. + * @param sis + * The list of StringInfo objects describing the positions where the + * String should be filled in. + * @param replace_bytes + * The unescaped bytes to be filled in. Escaping is performed by this + * method. + * @param tolerance + * The tolerance for line wrapping. The tolerance counts from the end + * of a StringInfo backwards to its start. If a word that starts + * within the tolerance doesn't fit, it is wrapped into the next + * line. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static void replacePlaceholderWithTolerance(byte[] pdf, List sis, + byte[] replace_bytes, int tolerance) throws PDFDocumentException + { + try + { + // String rep_str = new String(replace_bytes); + + SplitStrings ss = new SplitStrings(pdf, sis); + + int read_index = 0; + while (read_index < replace_bytes.length) + { + if (!ss.isValidLine()) + { + break; + } + + byte[] token = readToken(replace_bytes, read_index); + // String token_str = new String(token); + byte[] escaped_token = escapeToken(token); + + if (ss.fits(escaped_token)) + { + ss.write(escaped_token); + read_index += token.length; + continue; + } + else + { + if (ss.getAvailable() < tolerance) + { + ss.newline(); + continue; + } + else + { + // break the token + for (; read_index < replace_bytes.length; read_index++) + { + byte data = replace_bytes[read_index]; + + byte[] escaped_data = escapeByte(data); + + if (ss.fits(escaped_data)) + { + ss.write(escaped_data); + } + else + { + ss.newline(); + break; + } + } + continue; + + } + } + } + ss.fillRest(); + + if (read_index < replace_bytes.length) + { + logger_.error("The replace string was longer than the reserved placeholder."); + throw new PlaceholderException(null, replace_bytes.length - read_index); + } + + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + + } + + protected static byte[] readToken(byte[] bytes, int index) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (; index < bytes.length; index++) + { + byte data = bytes[index]; + + // byte [] escaped_data = escapeByte(data); + baos.write(data); + + if (canBreakAfter(data)) + { + break; + } + } + + return baos.toByteArray(); + } + + protected static byte[] escapeToken(byte[] token) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + for (int i = 0; i < token.length; i++) + { + byte[] escaped_data = escapeByte(token[i]); + baos.write(escaped_data); + } + + return baos.toByteArray(); + } +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java new file mode 100644 index 0000000..4bc1a1a --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: Pos.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +/** + * Encapsulation of a position on a PDF page. + * + * @author wprinz + */ +public class Pos +{ + + public float x; + + public float y; + + public float z; + + /** + * Default constructor. + */ + public Pos() + { + } + + /** + * Constructor that sets the coordinates. + * @param xx + * @param yy + * @param zz + */ + public Pos(float xx, float yy, float zz) + { + this.x = xx; + this.y = yy; + this.z = zz; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + this.x + "," + this.y + "," + this.z + ")"; + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java new file mode 100644 index 0000000..849d224 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: ReplaceInfo.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.util.List; + +import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; + +/** + * Holds the information requeired to replace a certain value in the document + * completely. + * + * @author wprinz + */ +public class ReplaceInfo implements Serializable +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = 7307210282876750431L; + + /** + * The field definition of this value. + */ + public SignatureFieldDefinition sfd = null; + + /** + * The value itself. + */ + public String value = null; + + /** + * The list of Strings this value must be splitted to. + */ + public List replaces = null; + + /** + * The brev of this value. + */ + public byte[] brev = null; + + /** + * The encoding of this value. + */ + public byte[] enc = null; + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java new file mode 100644 index 0000000..d7fcce9 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: SignatureHolder.java,v 1.3 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one signature block, which is + * the signed/signable text and the corresponding SignatureObject. + * + *

+ * Signators and Verifiactors should implement own classes for this interface + * that generate the text to be signed from the underlying data. For example a + * binary signature holder could generate the text to be signed by Base64 + * encoding the binary data. Furthermore this allows to cache the text to be + * signed. + *

+ * + * @author wprinz + */ +public interface SignatureHolder +{ + + /** + * Returns the signed text (verification) or the to-be-signed signable text + * (signation). + * + *

+ * Note that this text must be the one that was actually signed. This text is + * directly passed to the connector for signation/verification. No + * normalization or modification will be / must be done to this text between + * reading out from the signature holder and passing the text to the + * connector. + *

+ * + * @return Returns the signed text or the to-be-signed signable text. + */ + public String getSignedText(); + + /** + * + * @return Returns the SignatureObject containing the issuer, serial number, + * etc. + */ + public SignatureObject getSignatureObject(); + +} \ No newline at end of file diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java new file mode 100644 index 0000000..e3f75f1 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: SplitStrings.java,v 1.1 2006/08/30 14:02:35 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.List; + + +/** + * Class that helps filling out the placeholders. + * + *

+ * This class treats a sequence of placeholder StringInfos like a continuous + * data area that can be filled out regarding the boundaries. + *

+ * + * @author wprinz + */ +public class SplitStrings +{ + /** + * The byte used to fill unused bytes in the placeholders. + */ + public static final byte FILL_BYTE = 0; + + /** + * The underlying PDF. + */ + protected byte[] pdf = null; + + /** + * The strings to be filled out. + */ + protected StringInfo[] strings = null; + + /** + * The current string which is written to. + */ + protected int cur_string = 0; + + /** + * The current write position within the current string. + */ + protected int cur_pos = 0; + + /** + * Constructor. + * + * @param pdf + * The underlying PDF. + * @param strings + * The strings to be filled out. + */ + public SplitStrings(byte[] pdf, List strings) + { + this.pdf = pdf; + this.strings = new StringInfo[strings.size()]; + for (int i = 0; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + this.strings[i] = si; + } + } + + /** + * Returns how many bytes are still available in the current string. + * + * @return Returns the number of bytes that are still available. (positive + * integer, or zero if none are available) + */ + public int getAvailable() + { + return this.strings[this.cur_string].string_length - this.cur_pos; + } + + /** + * Tells, if the whole data would fit into the current string. + * + * @param data + * The data to be matched for fitting + * @return Returns true, if the whole data fits, false otherwise. + */ + public boolean fits(byte[] data) + { + return getAvailable() >= data.length; + } + + /** + * Writes the data into the current string. + * + *

+ * Note that the data must fit in. + *

+ * @param data The data to be written. + */ + public void write(byte[] data) + { + if (!fits(data)) + { + throw new IllegalArgumentException("The data doesn't fit in."); + } + + System.arraycopy(data, 0, pdf, this.strings[this.cur_string].string_start + this.cur_pos, data.length); + + this.cur_pos += data.length; + } + + /** + * Fills the current string with the fill character and moves on to the next + * string. + * + */ + public void newline() + { + int end = this.strings[this.cur_string].string_start + this.strings[this.cur_string].string_length; + for (int i = this.strings[this.cur_string].string_start + this.cur_pos; i < end; i++) + { + pdf[i] = FILL_BYTE; + } + + this.cur_string++; + this.cur_pos = 0; + } + + /** + * Fills all rest bytes with the fill character. + * + *

+ * This should be called when everything is finished to fill all strings properly. + *

+ */ + public void fillRest() + { + while (this.cur_string < this.strings.length) + { + newline(); + } + } + + /** + * Tells, if the current line is valid. + * @return Returns true, if this is a line that can be written to. + */ + public boolean isValidLine () + { + return this.cur_string < this.strings.length; + } +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java new file mode 100644 index 0000000..5933e4b --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: StringInfo.java,v 1.2 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; + +/** + * Specifies a certain data area within the pdf. + * + *

+ * Actually this is a byte range, which is used to hold the placeholder ranges + * for later replacement. + *

+ * + * @author wprinz + */ +public class StringInfo implements Serializable +{ + /** + * SVUID. + */ + private static final long serialVersionUID = 5834801907046737048L; + + /** + * The PDF document this range belongs to. + */ + protected byte[] pdf = null; + + /** + * The start offset of the range. + */ + public int string_start = -1; + + /** + * The length of the range. + */ + public int string_length = -1; + + /** + * Copies the bytes of this range to a new byte array. + * + * @return Returns the new byte array. + */ + public byte[] copyStringBytes() + { + byte[] bytes = new byte[this.string_length]; + System.arraycopy(this.pdf, this.string_start, bytes, 0, this.string_length); + return bytes; + } + + /** + * Converts the range into a String. + * + * @return Returns the String. + * @throws UnsupportedEncodingException + * Forwarded exception. + */ + public String getString(String encoding) throws UnsupportedEncodingException + { + byte[] bytes = copyStringBytes(); + return new String(bytes, encoding); + } + + public String toString() + { + try + { + return "(" + this.string_start + "," + this.string_length + ")" + getString("ISO-8859-1"); + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return "(" + this.string_start + "," + this.string_length + ")"; + } + } + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java new file mode 100644 index 0000000..ba55cdf --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: TablePos.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +/** + * Class that holds the exact position where the table should be written to the + * document. + * + * @author wprinz + */ +public class TablePos implements Serializable +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -5299027706623518059L; + + /** + * The page on which the block should be displayed. + */ + public int page = 0; + + /** + * The x position. + */ + public float pos_x = 0.0f; + + /** + * The y position. + */ + public float pos_y = 0.0f; + + /** + * The width of the block. + */ + public float width = 0.0f; + +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java new file mode 100644 index 0000000..140a6c3 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: TextualSignature.java,v 1.4 2006/10/31 08:12:45 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.pdfbox.pdfparser.PDFParser; +import org.pdfbox.pdmodel.PDDocument; +import org.pdfbox.util.PDFTextStripper; + +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; + +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfImportedPage; +import com.lowagie.text.pdf.PdfReader; +import com.lowagie.text.pdf.PdfWriter; + +/** + * Contains helper function for textual signatures. + * + * @author wprinz + */ +public class TextualSignature +{ + + /** + * Extracts the document text from a given pdf. + * + * @param pdf_stream + * The pdf_input stream. + * @return Returns the extracted document text. + * @throws PresentableException + * Forwarded exception. + */ + public static String extractTextTextual(InputStream pdf_stream) throws PresentableException + { + try + { + // logger_.debug("===================================================="); + // logger_.debug("extractText:"); + + // For text extraction, create a temporary object with iText just as the + // one + // created + // when being signed, but of course without adding content. + + + byte[] bytes = normalizePDF(pdf_stream); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + + PDFParser parser = new PDFParser(bais); + File temporary_dir = SettingsReader.getTemporaryDirectory(); + parser.setTempDirectory(temporary_dir); + parser.parse(); + + PDDocument doc = parser.getPDDocument(); + + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setSortByPosition(false); + // stripper.setStartPage(4); + // stripper.setEndPage(4); + String text = stripper.getText(doc); + + doc.close(); + + // logger_.debug("text.length = " + text.length()); + // logger_.debug("===================================================="); + + return text; + + } + catch (Exception e) + { + throw new PresentableException(e); + } + } + + /** + * Normalizes a given binary PDF to a version PDFbox can handle correctly. + * + *

+ * PDFbox has serious problems with documents that use incremental updates or + * XObject forms. Therefor use this to remove incremental updates and create a + * streamlined document. + *

+ * + *

+ * Note that this has nothing to do with text normalization. It just unifies + * the PDF documents that are fed into PDFbox for text extraction and page + * length determination. + *

+ * + * @param input_pdf + * The input pdf to be normalized. + * @return Returns the normalized pdf. + * @throws IOException + * @throws DocumentException + */ + public static byte[] normalizePDF(InputStream input_pdf) throws IOException, DocumentException + { + PdfReader reader = new PdfReader(input_pdf); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // For some reason the Reader -> ImportPage -> Writer mechanism produces + // problems en mass. + // The text extractor may not be able to extract proper text from + // documents + // created with + // this method (although it works when a Table is appended)... very + // fragile. + + Document document = new Document(); + + PdfWriter writer = PdfWriter.getInstance(document, baos); + document.open(); + + PdfContentByte cb = writer.getDirectContent(); + for (int page_num = 1; page_num <= reader.getNumberOfPages(); page_num++) + { + Rectangle new_size = reader.getPageSize(page_num); + document.setPageSize(new_size); + document.newPage(); + + PdfImportedPage page = writer.getImportedPage(reader, page_num); + + // note that this will add an xobject form to the doc. + // the xobject form contains the content of the page. + cb.addTemplate(page, 0, 0); + + // wprinz: debugging + // cb.beginText(); + // cb.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, + // BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 14); + // cb.showText("page " + page_num); + // cb.endText(); + // wprinz: end debugging + } + + document.close(); + + // for (int i = 1; i <= reader.getNumberOfPages(); i++) + // { + // Rectangle rect = reader.getBoxSize(i, "bleed"); + // logger_.debug("rect[" + i + "] = " + rect); + // } + + baos.close(); + byte[] normalizedPDF = baos.toByteArray(); + + return normalizedPDF; + } +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java new file mode 100644 index 0000000..fd56125 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: TextualSignatureHolder.java,v 1.1 2006/10/11 07:58:17 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one signature block, which is + * the signed/signable text and the corresponding SignatureObject. + * + * @author wprinz + */ +public class TextualSignatureHolder implements Serializable, SignatureHolder +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -7208103904479272760L; + + /** + * The signed text of this object. + * + *

+ * This is the value that will be signed by the Connector. + *

+ */ + private String signed_text = null; + + /** + * The signature object. + */ + private SignatureObject signature_object = null; + + public TextualSignatureHolder(String text, SignatureObject so) + { + this.signed_text = text; + this.signature_object = so; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignedText() + */ + public String getSignedText() + { + return this.signed_text; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignatureObject() + */ + public SignatureObject getSignatureObject() + { + return this.signature_object; + } +} diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java new file mode 100644 index 0000000..c075d45 --- /dev/null +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2006 by Know-Center, Graz, Austria + * + * This software is the confidential and proprietary information of Know-Center, + * Graz, Austria. You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Know-Center. + * + * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF + * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. + * + * $Id: Utils.java,v 1.3 2006/10/31 08:13:02 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Abstract class that contains helpful utility functions used by the digital + * signatures. + * + * @author wprinz + */ +public abstract class Utils +{ + + /** + * Extracts the pure content text from a given content stream. + * + *

+ * The pure content text is just an assembly of all strings that occur within the content stream in + * stream order. + * Each of these strings will be set on a new line. + *

+ * + * @param stream_bytes The content stream. + * @return Returns the extracted string. + * @throws IOException Forwarded exception. + */ + public static String extractPureTextFromContentStream( + final byte[] stream_bytes) throws IOException + { + + // logger_.debug("stream_bytes:"); + // logger_.debug(new String(stream_bytes, "US-ASCII")); + // logger_.debug(":end of stream_bytes"); + + final byte OPEN = '('; + final byte CLOSE = ')'; + + StringWriter strwrtr = new StringWriter(); + PrintWriter printer = new PrintWriter(strwrtr); + int open_index = -1; + int close_index = -1; + for (int i = 0; i < stream_bytes.length; i++) + { + if (stream_bytes[i] == OPEN) + { + open_index = i; + continue; + } + if (stream_bytes[i] == CLOSE) + { + close_index = i; + + // logger_.debug("open = " + open_index + ", close = " + + // close_index); + + int len = close_index - open_index - 1; + // logger_.debug("len = " + len); + + byte[] bytes = new byte[len]; + System.arraycopy(stream_bytes, open_index + 1, bytes, 0, len); + + String str = new String(bytes, "ISO-8859-1"); + // logger_.debug("string = " + str); + + printer.println(str); + + continue; + } + } + strwrtr.close(); + String signature_text = new String(strwrtr.getBuffer()); + // logger_.debug(signature_text); + + return signature_text; + } + +} -- cgit v1.2.3