From 06b900642342afb680aab3b36fba9247e1e29aa9 Mon Sep 17 00:00:00 2001 From: pdanner Date: Fri, 26 Nov 2010 12:02:47 +0000 Subject: Placeholder-image handling git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@613 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../PDF-AS-3.2-Anwendungsbeschreibung.doc | Bin 0 -> 597504 bytes .../PDF-AS-3.2-Anwendungsbeschreibung.pdf | Bin 0 -> 1138810 bytes dok/Anwendungsbeschreibung/qr-code-examples.zip | Bin 0 -> 560119 bytes pom.xml | 10 + .../java/at/gv/egiz/pdfas/commandline/Main.java | 95 ++++++- .../at/gv/egiz/pdfas/exceptions/ErrorCode.java | 2 + .../framework/PlaceholderExtractionException.java | 56 ++++ .../placeholder/SignaturePlaceholderContext.java | 49 ++++ .../placeholder/SignaturePlaceholderData.java | 129 +++++++++ .../placeholder/SignaturePlaceholderExtractor.java | 299 +++++++++++++++++++++ .../java/at/gv/egiz/pdfas/utils/PDFASUtils.java | 9 + .../java/at/knowcenter/wag/egov/egiz/PdfAS.java | 42 ++- .../wag/egov/egiz/pdf/BinarySignature.java | 28 ++ .../at/gv/egiz/pdfas/placeholder/empty.jpg | Bin 0 -> 631 bytes .../pdfas/placeholder/pdfbox-reader.properties | 23 ++ 15 files changed, 731 insertions(+), 11 deletions(-) create mode 100644 dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.doc create mode 100644 dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.pdf create mode 100644 dok/Anwendungsbeschreibung/qr-code-examples.zip create mode 100644 src/main/java/at/gv/egiz/pdfas/exceptions/framework/PlaceholderExtractionException.java create mode 100644 src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderContext.java create mode 100644 src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderData.java create mode 100644 src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderExtractor.java create mode 100644 src/main/resources/at/gv/egiz/pdfas/placeholder/empty.jpg create mode 100644 src/main/resources/at/gv/egiz/pdfas/placeholder/pdfbox-reader.properties diff --git a/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.doc b/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.doc new file mode 100644 index 0000000..19f43e1 Binary files /dev/null and b/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.doc differ diff --git a/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.pdf b/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.pdf new file mode 100644 index 0000000..fd58ad4 Binary files /dev/null and b/dok/Anwendungsbeschreibung/PDF-AS-3.2-Anwendungsbeschreibung.pdf differ diff --git a/dok/Anwendungsbeschreibung/qr-code-examples.zip b/dok/Anwendungsbeschreibung/qr-code-examples.zip new file mode 100644 index 0000000..9a6f454 Binary files /dev/null and b/dok/Anwendungsbeschreibung/qr-code-examples.zip differ diff --git a/pom.xml b/pom.xml index ae023aa..09603aa 100644 --- a/pom.xml +++ b/pom.xml @@ -360,6 +360,16 @@ ognl 2.6.9 + + com.google.zxing + core + 1.6-SNAPSHOT + + + com.google.zxing + javase + 1.6-SNAPSHOT + diff --git a/src/main/java/at/gv/egiz/pdfas/commandline/Main.java b/src/main/java/at/gv/egiz/pdfas/commandline/Main.java index 161dee8..40d0e5a 100644 --- a/src/main/java/at/gv/egiz/pdfas/commandline/Main.java +++ b/src/main/java/at/gv/egiz/pdfas/commandline/Main.java @@ -79,6 +79,7 @@ import at.knowcenter.wag.egov.egiz.web.servlets.VerifyServlet; */ public abstract class Main { +// 23.11.2010 changed by exthex - added parameters for placeholder handling /** * Command line parameter setting the application mode sign|verify */ @@ -113,6 +114,21 @@ public abstract class Main * Command line parameter selecting the position of the signature. */ protected static final String PARAMETER_POS = "-pos"; + + /** + * Command line parameter signaling to search the source document for a placeholder for the signature + */ + protected static final String PARAMETER_SEARCH_PLACEHOLDER = "-checkforplaceholder"; + + /** + * Command line parameter selecting the id of the placeholder to use + */ + protected static final String PARAMETER_PLACEHOLDER_ID = "-placeholder_id"; + + /** + * Command line parameter selecting the match mode for the placeholder + */ + protected static final String PARAMETER_PLACEHOLDER_MATCH_MODE = "-placeholder_matchmode"; /** * Command line parameter selecting the signature which is going to be @@ -157,6 +173,21 @@ public abstract class Main */ public static final String VALUE_SIGNATURE_MODE_DETACHED_TEXT = "detachedtextual"; + /** + * The placeholder match mode STRICT + */ + public static final String VALUE_PLACEHOLDER_MATCH_MODE_STRICT = "strict"; + + /** + * The placeholder match mode STRICT + */ + public static final String VALUE_PLACEHOLDER_MATCH_MODE_MODERATE = "moderate"; + + /** + * The placeholder match mode STRICT + */ + public static final String VALUE_PLACEHOLDER_MATCH_MODE_LENIENT = "lenient"; + /** * The log. */ @@ -207,6 +238,10 @@ public abstract class Main String user_name = null; String user_password = null; String pos_string = null; + + boolean search_placeholder = false; + String placeholderId = null; + int placeholderMatchMode = SignParameters.PLACEHOLDER_MATCH_MODE_MODERATE; int verify_which = -1; @@ -300,6 +335,44 @@ public abstract class Main continue; } + if (cur_arg.equals(PARAMETER_SEARCH_PLACEHOLDER)) + { + search_placeholder = true; + continue; + } + + if (cur_arg.equals(PARAMETER_PLACEHOLDER_ID)) + { + i++; + if (i >= args.length) + { + printNoValue(PARAMETER_PLACEHOLDER_ID); + return; + } + placeholderId = args[i]; + continue; + } + + if (cur_arg.equals(PARAMETER_PLACEHOLDER_MATCH_MODE)) + { + i++; + if (i >= args.length) + { + printNoValue(PARAMETER_PLACEHOLDER_MATCH_MODE); + return; + } + String matchMode = args[i]; + if (matchMode.equals(VALUE_PLACEHOLDER_MATCH_MODE_LENIENT)) + placeholderMatchMode = SignParameters.PLACEHOLDER_MATCH_MODE_LENIENT; + else if (matchMode.equals(VALUE_PLACEHOLDER_MATCH_MODE_MODERATE)) + placeholderMatchMode = SignParameters.PLACEHOLDER_MATCH_MODE_MODERATE; + else if (matchMode.equals(VALUE_PLACEHOLDER_MATCH_MODE_STRICT)) + placeholderMatchMode = SignParameters.PLACEHOLDER_MATCH_MODE_STRICT; + else + printUnrecognizedValue(PARAMETER_PLACEHOLDER_MATCH_MODE, args[i]); + continue; + } + if (cur_arg.equals(PARAMETER_USER_NAME)) { i++; @@ -435,7 +508,7 @@ public abstract class Main output = generateOutputFileNameFromInput(input, signature_mode); } - carryOutCommand(mode, signature_mode, connector, signature_type, user_name, user_password, verify_which, input, output, pos_string); + carryOutCommand(mode, signature_mode, connector, signature_type, user_name, user_password, verify_which, input, output, pos_string, search_placeholder, placeholderId, placeholderMatchMode); } catch (PdfAsException e) @@ -460,7 +533,7 @@ public abstract class Main } protected static void carryOutCommand(final String mode, final String signature_mode, final String connector, final String signature_type, final String user_name, final String user_password, - final int verify_which, final String input, String output, final String pos_string) throws PdfAsException + final int verify_which, final String input, String output, final String pos_string, boolean search_placeholder, String placeholderId, int placeholderMatchMode) throws PdfAsException { // File file = new File(input); // @@ -481,7 +554,7 @@ public abstract class Main if (mode.equals(VALUE_MODE_SIGN)) { - carryOutSign(input, connector, signature_mode, signature_type, pos_string, user_name, user_password, output, messageOutput); + carryOutSign(input, connector, signature_mode, signature_type, pos_string, user_name, user_password, output, messageOutput, search_placeholder, placeholderId, placeholderMatchMode); } else { @@ -491,7 +564,7 @@ public abstract class Main } public static void carryOutSign(String input, String connector, String signature_mode, String signature_type, String pos_string, String user_name, String user_password, String output, - PrintWriter messageOutput) throws PdfAsException + PrintWriter messageOutput, boolean search_placeholder, String placeholderId, int placeholderMatchMode) throws PdfAsException { messageOutput.println("Signing " + input + "..."); @@ -532,7 +605,7 @@ public abstract class Main } try { - processSign(dataSource, connector, signature_mode, signature_type, pos_string, dataSink); + processSign(dataSource, connector, signature_mode, signature_type, pos_string, search_placeholder, placeholderId, placeholderMatchMode, dataSink); } catch (Exception e) { // Exception caught in order to delete file based datasink if (outputFile != null && outputFile.exists()) @@ -626,7 +699,7 @@ public abstract class Main } - public static void processSign(DataSource dataSource, String connector, String signature_mode, String signature_type, String pos_string, DataSink dataSink) throws PdfAsException + public static void processSign(DataSource dataSource, String connector, String signature_mode, String signature_type, String pos_string, boolean search_placeholder, String placeholderId, int placeholderMatchMode, DataSink dataSink) throws PdfAsException { TablePos pos = null; if (pos_string != null) @@ -678,6 +751,9 @@ public abstract class Main sp.setSignatureDevice(connector); sp.setSignatureProfileId(signature_type); sp.setSignaturePositioning(posi); + sp.setCheckForPlaceholder(search_placeholder); + sp.setPlaceholderId(placeholderId); + sp.setPlaceholderMatchMode(placeholderMatchMode); pdfAs.sign(sp); } @@ -979,6 +1055,13 @@ public abstract class Main writer.println(" 'new' ... new page"); writer.println(" intvalue ... pagenumber must be > 0 if p>number of pages in document p-->handled like p:'new'"); writer.println(" f_algo floatvalue ... consider footerline must be >= 0 (only if y_algo is auto and p_algo is not 'new')"); + + writer.println(" " + PARAMETER_SEARCH_PLACEHOLDER + " ... [optional] if set, the source document will be scanned for signature placeholder images"); + writer.println(" " + PARAMETER_PLACEHOLDER_ID + " ... [optional] search for signature placeholder images containing the given id"); + writer.println(" " + PARAMETER_PLACEHOLDER_MATCH_MODE + " <" + VALUE_PLACEHOLDER_MATCH_MODE_LENIENT + "|" + VALUE_PLACEHOLDER_MATCH_MODE_MODERATE + "|" + VALUE_PLACEHOLDER_MATCH_MODE_STRICT + "> ... [optional] specify the behaviour if no matching placeholder could be found. Default is ."); + writer.println(" " + VALUE_PLACEHOLDER_MATCH_MODE_LENIENT + " ... sign in place of the first found placeholder, regardless if it matches exactly, or at the end of the document if none is found."); + writer.println(" " + VALUE_PLACEHOLDER_MATCH_MODE_MODERATE + " ... sign in place of the first found placeholder which has no explicit id set, or at the end of the document if none is found."); + writer.println(" " + VALUE_PLACEHOLDER_MATCH_MODE_STRICT + " ... throws a PlaceholderExtractionException."); writer.println(" OPTIONS for verification:"); writer.println(" " + PARAMETER_VERIFY_WHICH + " ... [optional] zero based number of the signature"); diff --git a/src/main/java/at/gv/egiz/pdfas/exceptions/ErrorCode.java b/src/main/java/at/gv/egiz/pdfas/exceptions/ErrorCode.java index bf98c85..89c4e07 100644 --- a/src/main/java/at/gv/egiz/pdfas/exceptions/ErrorCode.java +++ b/src/main/java/at/gv/egiz/pdfas/exceptions/ErrorCode.java @@ -32,6 +32,8 @@ public final class ErrorCode public static final int FONT_NOT_FOUND = 230; public static final int DOCUMENT_IS_PROTECTED = 231; public static final int INVALID_SIGNATURE_DICTIONARY = 232; +//23.11.2010 changed by exthex - added error code for failed extraction + public static final int SIGNATURE_PLACEHOLDER_EXTRACTION_FAILED = 233; public static final int INVALID_SIGNATURE_POSITION = 224; diff --git a/src/main/java/at/gv/egiz/pdfas/exceptions/framework/PlaceholderExtractionException.java b/src/main/java/at/gv/egiz/pdfas/exceptions/framework/PlaceholderExtractionException.java new file mode 100644 index 0000000..93eda36 --- /dev/null +++ b/src/main/java/at/gv/egiz/pdfas/exceptions/framework/PlaceholderExtractionException.java @@ -0,0 +1,56 @@ +package at.gv.egiz.pdfas.exceptions.framework; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; + +/** + * Exceptions thrown if STRICT matching mode for placeholder extraction is set and no placeholder could be found. + * + * @author exthex + * + */ +public class PlaceholderExtractionException extends PresentableException +{ + private static final long serialVersionUID = 0L; + + /** + * Constructor. + * + * @param errorCode the error code + * @param message the additional message + * @param cause the causing exception + * + * @see ErrorCode#SIGNATURE_PLACEHOLDER_EXTRACTION_FAILED + */ + public PlaceholderExtractionException(int errorCode, String message, Throwable cause) + { + super(errorCode, message, cause); + } + + /** + * Constructor. + * + * @param errorCode the error code + * @param message the additional message + * + * @see ErrorCode#SIGNATURE_PLACEHOLDER_EXTRACTION_FAILED + */ + public PlaceholderExtractionException(int errorCode, String message) + { + super(errorCode, message); + } + + /** + * Constructor. + * + * @param errorCode the error code + * @param cause the causing exception + * + * @see ErrorCode#SIGNATURE_PLACEHOLDER_EXTRACTION_FAILED + */ + public PlaceholderExtractionException(int errorCode, Throwable cause) + { + super(errorCode, cause); + } + +} diff --git a/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderContext.java b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderContext.java new file mode 100644 index 0000000..e8c157c --- /dev/null +++ b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderContext.java @@ -0,0 +1,49 @@ +package at.gv.egiz.pdfas.placeholder; + +/** + * Store and retrieve {@link SignaturePlaceholderData} in/from a thread local context. + * + * @author exthex + * + */ +public class SignaturePlaceholderContext { + + private ThreadLocal sigHolder = new ThreadLocal(); + + private static SignaturePlaceholderContext instance = new SignaturePlaceholderContext(); + + /** + * Constructor. Private because this is a singleton. + */ + private SignaturePlaceholderContext() { + + } + + /** + * Get the {@link SignaturePlaceholderData} which is currently bound to this thread. + * Might be null. + * + * @return + */ + public static SignaturePlaceholderData getSignaturePlaceholderData(){ + return (SignaturePlaceholderData)instance.sigHolder.get(); + } + + /** + * + * @return true if there is currently a {@link SignaturePlaceholderData} bound to this thread, false otherwise. + */ + public static boolean isSignaturePlaceholderDataSet() { + return instance.sigHolder.get() != null; + } + + /** + * Bind a {@link SignaturePlaceholderData} to this thread. + * If the given data is null, the context will be cleared. + * + * @param data if null, clears the ThreadLocal, else binds the data to the current thread. + */ + public static void setSignaturePlaceholderData(SignaturePlaceholderData data) { + instance.sigHolder.set(data); + } +} diff --git a/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderData.java b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderData.java new file mode 100644 index 0000000..754b151 --- /dev/null +++ b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderData.java @@ -0,0 +1,129 @@ +package at.gv.egiz.pdfas.placeholder; + +import at.knowcenter.wag.egov.egiz.pdf.TablePos; + +/** + * This class represents all the data which can be extracted from a placeholder image. + * + * @author exthex + * + */ +public class SignaturePlaceholderData { + + public static final String ID_KEY = "id"; + + public static final String PROFILE_KEY = "profile"; + + public static final String TYPE_KEY = "type"; + + public static final String SIG_KEY_KEY = "key"; + + private String profile; + + private String type; + + private String key; + + private String id; + + private TablePos tablePos; + + private String placeholderName; + + /** + * + * @param profile + * @param type + * @param sigKey + * @param id + */ + public SignaturePlaceholderData(String profile, String type, String sigKey, String id) { + this.profile = profile; + this.type = type; + this.key = sigKey; + this.id = id; + } + + /** + * Get the table position for the signature block.
+ * The table position is created from the page number, the upper left corner and the width of the placeholder image. + * + * @return + */ + public TablePos getTablePos() { + return tablePos; + } + + void setTablePos(TablePos tablePos) { + this.tablePos = tablePos; + } + + /** + * The profile name. Might be null if not included in the qr-code. + * + * @return + */ + public String getProfile() { + return profile; + } + + void setProfile(String profile) { + this.profile = profile; + } + + /** + * The signature type: "textual" or "binary". Might be null if not included in the qr-code. + * @return + */ + public String getType() { + return type; + } + + void setType(String type) { + this.type = type; + } + + /** + * The key identifier for MOA signature. Might be null if not included in the qr-code. + * + * @return + */ + public String getKey() { + return key; + } + + void setKey(String key) { + this.key = key; + } + + public String toString() { + return getClass().toString() + ": profile=" + profile + "; type=" + type + "; sigKey=" + key + "; table pos=" + tablePos; + } + + void setPlaceholderName(String name) { + this.placeholderName = name; + } + + /** + * The name of the placeholder image. + * + * @return + */ + public String getPlaceholderName() { + return placeholderName; + } + + /** + * The id associated with this placeholder. + * + * @return + */ + public String getId() { + return id; + } + + void setId(String id) { + this.id = id; + } + +} diff --git a/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderExtractor.java b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderExtractor.java new file mode 100644 index 0000000..50c3281 --- /dev/null +++ b/src/main/java/at/gv/egiz/pdfas/placeholder/SignaturePlaceholderExtractor.java @@ -0,0 +1,299 @@ +package at.gv.egiz.pdfas.placeholder; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.exceptions.WrappedIOException; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject; +import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; +import org.apache.pdfbox.util.Matrix; +import org.apache.pdfbox.util.PDFOperator; +import org.apache.pdfbox.util.PDFStreamEngine; +import org.apache.pdfbox.util.ResourceLoader; + +import at.gv.egiz.pdfas.api.sign.SignParameters; +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.gv.egiz.pdfas.exceptions.framework.PlaceholderExtractionException; +import at.gv.egiz.pdfas.utils.PDFASUtils; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.pdf.TablePos; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; + +/** + * Extract all relevant information from a placeholder image. + * + * @author exthex + * + */ +public class SignaturePlaceholderExtractor extends PDFStreamEngine { + /** + * The log. + */ + private static Log log = LogFactory.getLog(SignaturePlaceholderExtractor.class); + + private List placeholders = new Vector(); + private int currentPage = 0; + + private SignaturePlaceholderExtractor(String placeholderId, int placeholderMatchMode) throws IOException { + super(ResourceLoader.loadProperties("at/gv/egiz/pdfas/placeholder/pdfbox-reader.properties", + true)); + } + + /** + * Search the document for placeholder images and possibly included + * additional info.
+ * Searches only for the first placeholder page after page from top. + * + * @param inputStream + * @return all available info from the first found placeholder. + * @throws PDFDocumentException if the document could not be read. + * @throws PlaceholderExtractionException if STRICT matching mode was requested and no suitable placeholder could be found. + */ + public static SignaturePlaceholderData extract(InputStream inputStream, String placeholderId, int matchMode) + throws PDFDocumentException, PlaceholderExtractionException { + SignaturePlaceholderContext.setSignaturePlaceholderData(null); + PDDocument doc = null; + try + { + try { + doc = PDDocument.load(inputStream); + } catch (IOException e) { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e); + } + PDFASUtils.checkDocumentPermissions(doc); + + SignaturePlaceholderExtractor extractor; + try + { + extractor = new SignaturePlaceholderExtractor(placeholderId, matchMode); + } catch (IOException e2) { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e2); + } + List pages = doc.getDocumentCatalog().getAllPages(); + Iterator iter = pages.iterator(); + int pageNr = 0; + while (iter.hasNext()) { + pageNr++; + PDPage page = (PDPage) iter.next(); + try { + extractor.setCurrentPage(pageNr); + extractor.processStream( page, page.findResources(), page.getContents().getStream() ); + SignaturePlaceholderData ret = matchPlaceholderPage(extractor.placeholders, placeholderId, matchMode); + if (ret != null){ + SignaturePlaceholderContext.setSignaturePlaceholderData(ret); + return ret; + } + } catch (IOException e1) { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e1); + } + + } + if (extractor.placeholders.size() > 0){ + SignaturePlaceholderData ret = matchPlaceholderDocument(extractor.placeholders, placeholderId, matchMode); + SignaturePlaceholderContext.setSignaturePlaceholderData(ret); + return ret; + } + return null; + }finally{ + if (doc != null) + try { + doc.close(); + } catch (IOException e) { + log.debug("Could not close document.", e); + } + } + + } + + private static SignaturePlaceholderData matchPlaceholderDocument( + List placeholders, String placeholderId, int matchMode) throws PlaceholderExtractionException { + + if (matchMode == SignParameters.PLACEHOLDER_MATCH_MODE_STRICT) + throw new PlaceholderExtractionException(ErrorCode.SIGNATURE_PLACEHOLDER_EXTRACTION_FAILED, "no suitable placeholder found and STRICT matching mode requested."); + + if (placeholders.size() == 0) + return null; + + for (int i = 0; i < placeholders.size(); i++) + { + SignaturePlaceholderData spd = (SignaturePlaceholderData)placeholders.get(i); + if (spd.getId() == null) + return spd; + } + + if (matchMode == SignParameters.PLACEHOLDER_MATCH_MODE_LENIENT) + return (SignaturePlaceholderData)placeholders.get(0); + + return null; + } + + private static SignaturePlaceholderData matchPlaceholderPage(List placeholders, + String placeholderId, int matchMode) { + if (placeholders.size() == 0) + return null; + for (int i = 0; i < placeholders.size(); i++) + { + SignaturePlaceholderData data = (SignaturePlaceholderData)placeholders.get(i); + if (placeholderId != null && placeholderId.equals(data.getId())) + return data; + if (placeholderId == null && data.getId() == null) + return data; + } + return null; + } + + private void setCurrentPage(int pageNr) { + this.currentPage = pageNr; + } + + protected void processOperator( PDFOperator operator, List arguments ) throws IOException + { + String operation = operator.getOperation(); + if( operation.equals( "Do" ) ) + { + COSName objectName = (COSName)arguments.get( 0 ); + Map xobjects = getResources().getXObjects(); + PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() ); + if( xobject instanceof PDXObjectImage ) + { + try + { + PDXObjectImage image = (PDXObjectImage)xobject; + SignaturePlaceholderData data = checkImage(image); + if (data != null) + { + PDPage page = getCurrentPage(); + Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); + double rotationInRadians = (page.findRotation() * Math.PI)/180; + + AffineTransform rotation = new AffineTransform(); + rotation.setToRotation( rotationInRadians ); + AffineTransform rotationInverse = rotation.createInverse(); + Matrix rotationInverseMatrix = new Matrix(); + rotationInverseMatrix.setFromAffineTransform( rotationInverse ); + Matrix rotationMatrix = new Matrix(); + rotationMatrix.setFromAffineTransform( rotation ); + + Matrix unrotatedCTM = ctm.multiply( rotationInverseMatrix ); + + float x = unrotatedCTM.getXPosition() - 1F; + float y = unrotatedCTM.getYPosition() + unrotatedCTM.getYScale() + 1F; + float w = unrotatedCTM.getXScale() + 2F; + + String posString = "p:" + currentPage + ";x:" + x + ";y:" + y + ";w:" + w; + try + { + data.setTablePos(new TablePos(posString)); + data.setPlaceholderName(objectName.getName()); + placeholders.add(data); + } catch (PDFDocumentException e) { + throw new WrappedIOException(e); + } + } + } + catch( NoninvertibleTransformException e ) + { + throw new WrappedIOException( e ); + } + } + } + else + { + super.processOperator( operator, arguments ); + } + } + + /** + * Checks an image if it is a placeholder for a signature. + * + * @param image + * @return + * @throws IOException + */ + private SignaturePlaceholderData checkImage(PDXObjectImage image) throws IOException { + BufferedImage bimg = image.getRGBImage(); + if(bimg.getHeight() < 10 || bimg.getWidth() < 10) + return null; + + LuminanceSource source = new BufferedImageLuminanceSource(bimg); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + Result result; + long before = System.currentTimeMillis(); + try { + Hashtable hints = new Hashtable(); + Vector formats = new Vector(); + formats.add(BarcodeFormat.QR_CODE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); + result = new MultiFormatReader().decode(bitmap, hints); + String text = result.getText(); + String profile = null; + String type = null; + String sigKey = null; + String id = null; + if (text != null && text.startsWith("PDF-AS-POS")) { + String[] data = text.split(";"); + if (data.length > 1) { + for (int i = 1; i < data.length; i++) { + String kvPair = data[i]; + String[] kv = kvPair.split("="); + if (kv.length != 2) { + log.debug("invalid parameter in placeholder data: " + kvPair); + } else { + if (kv[0].equalsIgnoreCase(SignaturePlaceholderData.ID_KEY)) { + id = kv[1]; + } else if (kv[0].equalsIgnoreCase(SignaturePlaceholderData.PROFILE_KEY)) { + profile = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.SIG_KEY_KEY)) { + sigKey = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.TYPE_KEY)) { + type = kv[1]; + } + } + } + } + return new SignaturePlaceholderData(profile, type, sigKey, id); + } + } catch (ReaderException re) { + if (log.isDebugEnabled()) { + log.debug("Could not decode - not a placeholder. needed: " + + (System.currentTimeMillis() - before)); + } + if (!(re instanceof NotFoundException)){ + if (log.isInfoEnabled()) { + log.info("Failed to decode image", re); + } + } + } catch(ArrayIndexOutOfBoundsException e){ + if (log.isInfoEnabled()) { + log.info("Failed to decode image. Probably a zxing bug", e); + } + } + return null; + } + +} diff --git a/src/main/java/at/gv/egiz/pdfas/utils/PDFASUtils.java b/src/main/java/at/gv/egiz/pdfas/utils/PDFASUtils.java index 9841779..f25e668 100644 --- a/src/main/java/at/gv/egiz/pdfas/utils/PDFASUtils.java +++ b/src/main/java/at/gv/egiz/pdfas/utils/PDFASUtils.java @@ -7,6 +7,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import org.apache.pdfbox.pdmodel.PDDocument; + import at.gv.egiz.pdfas.exceptions.ErrorCode; import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; @@ -16,6 +18,7 @@ import com.lowagie.text.pdf.PdfReader; * @author tknall */ public class PDFASUtils { +//23.11.2010 changed by exthex - added checkDocumentPermissions(PDDocument doc) private PDFASUtils() { } @@ -56,5 +59,11 @@ public class PDFASUtils { } return result; } + + public static void checkDocumentPermissions(PDDocument doc) throws PDFDocumentException { + if (doc.isEncrypted()) { + throw new PDFDocumentException(ErrorCode.DOCUMENT_IS_PROTECTED, "Document is encrypted."); + } + } } diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/PdfAS.java b/src/main/java/at/knowcenter/wag/egov/egiz/PdfAS.java index 57868d0..2c2c591 100644 --- a/src/main/java/at/knowcenter/wag/egov/egiz/PdfAS.java +++ b/src/main/java/at/knowcenter/wag/egov/egiz/PdfAS.java @@ -30,7 +30,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import at.gv.egiz.pdfas.api.analyze.NonTextObjectInfo; +import at.gv.egiz.pdfas.api.commons.SignatureInformation; import at.gv.egiz.pdfas.api.timestamp.TimeStamper; +import at.gv.egiz.pdfas.api.xmldsig.ExtendedSignatureInformation; +import at.gv.egiz.pdfas.api.xmldsig.XMLDsigData; import at.gv.egiz.pdfas.commandline.CommandlineConnectorChooser; import at.gv.egiz.pdfas.exceptions.ErrorCode; import at.gv.egiz.pdfas.exceptions.framework.CorrectorException; @@ -95,7 +98,8 @@ import com.lowagie.text.pdf.PdfReader; */ public abstract class PdfAS { - +//23.11.2010 changed by exthex - added method: verifyExtendedSignatureHolders(List extended_signature_info, String connectorType, boolean returnHashInputData, Date verificationTime) + /** * The current version of the pdf-as library. This version string is logged on every invocation * of the api or the web application. @@ -800,7 +804,7 @@ public abstract class PdfAS // (holder.signature_object.isTextual() ? "textual" : "binary")); // logger_.debug(holder.signature_object.toString()); - SignatureResponse result = verify(holder, connectorType, returnHashInputData, verificationTime); + SignatureResponse result = verify(holder, connectorType, returnHashInputData, verificationTime, null); results.add(result); // logger_.debug(); @@ -815,6 +819,34 @@ public abstract class PdfAS return results; } + /** + * + * @param extended_signature_info + * @param connectorType + * @param returnHashInputData + * @param verificationTime + * @return + * @throws PDFDocumentException + * @throws NormalizeException + * @throws SignatureException + * @throws ConnectorException + * @throws ConnectorFactoryException + */ + public static List verifyExtendedSignatureHolders(List extended_signature_info, String connectorType, boolean returnHashInputData, Date verificationTime) throws PDFDocumentException, NormalizeException, SignatureException, ConnectorException, ConnectorFactoryException + { + List results = new ArrayList(); + for (int i = 0; i < extended_signature_info.size(); i++) + { + ExtendedSignatureInformation sigInfo = (ExtendedSignatureInformation) extended_signature_info.get(i); + SignatureInformation si = sigInfo.getSignatureInformation(); + SignatureHolder holder = (SignatureHolder) si.getInternalSignatureInformation(); + + SignatureResponse result = verify(holder, connectorType, returnHashInputData, verificationTime, sigInfo.getXmlDsigData()); + results.add(result); + } + return results; + } + public static List verifySignatureHoldersWeb(List signature_holders, VerifySessionInformation si, String loc_ref) throws PDFDocumentException, NormalizeException, SignatureException, ConnectorException { @@ -852,7 +884,7 @@ public abstract class PdfAS * @throws ConnectorException * @throws ConnectorFactoryException */ - public static SignatureResponse verify(SignatureHolder signature_holder, String connectorType, boolean returnHashInputData, Date verificationTime) throws NormalizeException, PDFDocumentException, SignatureException, ConnectorException, ConnectorFactoryException + public static SignatureResponse verify(SignatureHolder signature_holder, String connectorType, boolean returnHashInputData, Date verificationTime, XMLDsigData dsig) throws NormalizeException, PDFDocumentException, SignatureException, ConnectorException, ConnectorFactoryException { // String text_to_be_verified = signature_holder.getSignedText(); // logger_.debug("verify text_to_be_verified"+text_to_be_verified); @@ -894,7 +926,7 @@ public abstract class PdfAS cp.setVerificationTime(verificationTime); Connector c = at.gv.egiz.pdfas.framework.ConnectorFactory.createConnector(connectorId, cp); - return c.doVerify(sd, so); + return c.doVerify(sd, so, dsig); } public static SignatureResponse verifyWeb(SignatureHolder signature_holder, String connector, String loc_ref) throws NormalizeException, PDFDocumentException, SignatureException, ConnectorException @@ -933,7 +965,7 @@ public abstract class PdfAS String profile = so_to_be_verified.getSignatureTypeDefinition().getType(); Connector c = ConnectorChooser.chooseWebConnectorForVerify(connector, so_to_be_verified.getKZ(), so.id, profile, loc_ref); - return c.doVerify(sd, so); + return c.doVerify(sd, so, null); } 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 index 4a80553..c34ee68 100644 --- a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java +++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; @@ -42,6 +43,8 @@ import at.gv.egiz.pdfas.exceptions.pdf.KZSettingNotFoundException; import at.gv.egiz.pdfas.framework.input.PdfDataSource; import at.gv.egiz.pdfas.framework.output.DataSink; import at.gv.egiz.pdfas.framework.signator.SignatorInformation; +import at.gv.egiz.pdfas.placeholder.SignaturePlaceholderContext; +import at.gv.egiz.pdfas.placeholder.SignaturePlaceholderData; import at.gv.egiz.pdfas.utils.PDFASUtils; import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; @@ -56,13 +59,17 @@ import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; import at.knowcenter.wag.egov.egiz.tools.CodingHelper; import at.knowcenter.wag.exactparser.ByteArrayUtils; +import com.lowagie.text.BadElementException; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; +import com.lowagie.text.Image; import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.BadPdfFormatException; 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.PdfImage; import com.lowagie.text.pdf.PdfIndirectObject; import com.lowagie.text.pdf.PdfIndirectReference; import com.lowagie.text.pdf.PdfName; @@ -87,6 +94,8 @@ import com.lowagie.text.pdf.PdfTemplate; */ public abstract class BinarySignature { +//23.11.2010 changed by exthex - added replacePlaceholder(PdfStamper stamper, int pageNr, String placeholderName) method + protected static Log logger = LogFactory.getLog(BinarySignature.class); /** * The tolerance area of the line break algorithm. @@ -826,6 +835,13 @@ public abstract class BinarySignature { throw new PDFDocumentException(224, "The provided page (=" + pi.getPage() + ") is out of range."); } + + if (SignaturePlaceholderContext.isSignaturePlaceholderDataSet() && + SignaturePlaceholderContext.getSignaturePlaceholderData().getPlaceholderName() != null) + { + replacePlaceholder(stamper, pi.getPage(), SignaturePlaceholderContext.getSignaturePlaceholderData().getPlaceholderName()); + } + PdfContentByte content = stamper.getOverContent(pi.getPage()); // content = StampContent einer PageStamp. @@ -940,6 +956,18 @@ public abstract class BinarySignature throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, e); } } + + private static void replacePlaceholder(PdfStamper stamper, int pageNr, String placeholderName) throws BadElementException, MalformedURLException, IOException, BadPdfFormatException { + Image img = Image.getInstance(SignaturePlaceholderData.class.getResource("empty.jpg")); + PdfImage pImg = new PdfImage(img, "Imwurscht", null); + PdfStamperImp stamperImp = (PdfStamperImp)stamper.getWriter(); + PdfIndirectObject ind = stamperImp.addToBody(pImg); + + PdfDictionary resources = stamper.getReader().getPageN(pageNr).getAsDict(PdfName.RESOURCES); + resources.getAsDict(PdfName.XOBJECT).put(new PdfName(placeholderName), ind.getIndirectReference()); + stamperImp.markUsed(resources); + } + /** * Creates the EGIZ Dictionary and adds it to the document. diff --git a/src/main/resources/at/gv/egiz/pdfas/placeholder/empty.jpg b/src/main/resources/at/gv/egiz/pdfas/placeholder/empty.jpg new file mode 100644 index 0000000..7913177 Binary files /dev/null and b/src/main/resources/at/gv/egiz/pdfas/placeholder/empty.jpg differ diff --git a/src/main/resources/at/gv/egiz/pdfas/placeholder/pdfbox-reader.properties b/src/main/resources/at/gv/egiz/pdfas/placeholder/pdfbox-reader.properties new file mode 100644 index 0000000..a3decc9 --- /dev/null +++ b/src/main/resources/at/gv/egiz/pdfas/placeholder/pdfbox-reader.properties @@ -0,0 +1,23 @@ +BT = org.apache.pdfbox.util.operator.BeginText +cm = org.apache.pdfbox.util.operator.Concatenate +Do = org.apache.pdfbox.util.operator.Invoke +ET = org.apache.pdfbox.util.operator.EndText +gs = org.apache.pdfbox.util.operator.SetGraphicsStateParameters +q = org.apache.pdfbox.util.operator.GSave +Q = org.apache.pdfbox.util.operator.GRestore +T* = org.apache.pdfbox.util.operator.NextLine +Tc = org.apache.pdfbox.util.operator.SetCharSpacing +Td = org.apache.pdfbox.util.operator.MoveText +TD = org.apache.pdfbox.util.operator.MoveTextSetLeading +Tf = org.apache.pdfbox.util.operator.SetTextFont +Tj = org.apache.pdfbox.util.operator.ShowText +TJ = org.apache.pdfbox.util.operator.ShowTextGlyph +TL = org.apache.pdfbox.util.operator.SetTextLeading +Tm = org.apache.pdfbox.util.operator.SetMatrix +Tr = org.apache.pdfbox.util.operator.SetTextRenderingMode +Ts = org.apache.pdfbox.util.operator.SetTextRise +Tw = org.apache.pdfbox.util.operator.SetWordSpacing +Tz = org.apache.pdfbox.util.operator.SetHorizontalTextScaling +w = org.apache.pdfbox.util.operator.SetLineWidth +\' = org.apache.pdfbox.util.operator.MoveAndShow +\" = org.apache.pdfbox.util.operator.SetMoveAndShow \ No newline at end of file -- cgit v1.2.3