Implemented validating input streams in addition to files.

This commit is contained in:
Dominik Röschke
2024-05-02 11:25:56 +02:00
parent 43d979436a
commit 58ec7e2e98
4 changed files with 297 additions and 145 deletions

View File

@@ -29,9 +29,9 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.deploy.skip>false <maven.deploy.skip>false
</maven.deploy.skip><!-- do deploy to maven central, parent project does not and inherits --> </maven.deploy.skip><!-- do deploy to maven central, parent project does not and inherits -->
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion> <maven.compiler.compilerVersion>21</maven.compiler.compilerVersion>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
</properties> </properties>
<dependencies> <dependencies>
@@ -190,8 +190,8 @@
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven <!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
mvn clean compile assembly:single --> mvn clean compile assembly:single -->
<!-- or whatever version you use --> <!-- or whatever version you use -->
<source>8</source> <source>9</source>
<target>8</target> <target>9</target>
</configuration> </configuration>
</plugin> </plugin>
<!-- /ZUV --> <!-- /ZUV -->

View File

@@ -0,0 +1,28 @@
package org.mustangproject.validator;
public final class ByteArraySearcher {
private ByteArraySearcher() {
}
public static boolean contains(byte[] haystack, byte[] needle) {
if (needle.length > haystack.length) {
return false;
}
for (int i = 0; i <= haystack.length - needle.length; i++) {
boolean found = true;
for (int j = 0; j < needle.length; j++) {
if (haystack[i + j] != needle[j]) {
found = false;
break;
}
}
if (found) {
return true;
}
}
return false;
}
}

View File

@@ -1,8 +1,10 @@
package org.mustangproject.validator; package org.mustangproject.validator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
@@ -37,10 +39,12 @@ import org.verapdf.pdfa.validation.validators.ValidatorConfig;
import org.verapdf.pdfa.validation.validators.ValidatorFactory; import org.verapdf.pdfa.validation.validators.ValidatorFactory;
import org.verapdf.processor.BatchProcessor; import org.verapdf.processor.BatchProcessor;
import org.verapdf.processor.FormatOption; import org.verapdf.processor.FormatOption;
import org.verapdf.processor.ItemProcessor;
import org.verapdf.processor.ProcessorConfig; import org.verapdf.processor.ProcessorConfig;
import org.verapdf.processor.ProcessorFactory; import org.verapdf.processor.ProcessorFactory;
import org.verapdf.processor.TaskType; import org.verapdf.processor.TaskType;
import org.verapdf.processor.plugins.PluginsCollectionConfig; import org.verapdf.processor.plugins.PluginsCollectionConfig;
import org.verapdf.processor.reports.ItemDetails;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
@@ -50,14 +54,14 @@ public class PDFValidator extends Validator {
public PDFValidator(ValidationContext ctx) { public PDFValidator(ValidationContext ctx) {
super(ctx); super(ctx);
// TODO Auto-generated constructor stub
} }
private static final Logger LOGGER = LoggerFactory.getLogger(PDFValidator.class.getCanonicalName()); // log output private static final Logger LOGGER = LoggerFactory.getLogger(PDFValidator.class.getCanonicalName()); // log output
// is
private String pdfFilename; private String pdfFilename;
private byte[] fileContents;
private String pdfReport; private String pdfReport;
private String Signature; private String Signature;
@@ -69,17 +73,13 @@ public class PDFValidator extends Validator {
} }
@Override @Override
public void validate() throws IrrecoverableValidationError { public void validate() throws IrrecoverableValidationError {
zfXML = null; zfXML = null;
final File file = new File(pdfFilename);
// file existence must have been checked before // file existence must have been checked before
final BigFileSearcher searcher = new BigFileSearcher(); if (!ByteArraySearcher.contains(fileContents, new byte[]{'%', 'P', 'D', 'F'})) {
final byte[] pdfSignature = { '%', 'P', 'D', 'F' };
if (searcher.indexOf(file, pdfSignature) != 0) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.fatal, "Not a PDF file "+pdfFilename).setSection(20).setPart(EPart.pdf)); new ValidationResultItem(ESeverity.fatal, "Not a PDF file " + pdfFilename).setSection(20).setPart(EPart.pdf));
} }
@@ -103,33 +103,29 @@ public class PDFValidator extends Validator {
// tasks.add(TaskType.FIX_METADATA); // tasks.add(TaskType.FIX_METADATA);
// Creating processor config // Creating processor config
final ProcessorConfig processorConfig = ProcessorFactory.fromValues(validatorConfig, featureConfig, pluginsConfig, final ProcessorConfig processorConfig = ProcessorFactory.fromValues(validatorConfig, featureConfig, pluginsConfig,
fixerConfig, tasks); fixerConfig, tasks
);
// Creating processor and output stream. // Creating processor and output stream.
final ByteArrayOutputStream reportStream = new ByteArrayOutputStream(); final ByteArrayOutputStream reportStream = new ByteArrayOutputStream();
try (BatchProcessor processor = ProcessorFactory.fileBatchProcessor(processorConfig)) { final InputStream inputStream = new ByteArrayInputStream(fileContents);
try (ItemProcessor processor = ProcessorFactory.createProcessor(processorConfig)) {
// Generating list of files for processing // Generating list of files for processing
final List<File> files = new ArrayList<>();
files.add(new File(pdfFilename));
// starting the processor // starting the processor
processor.process(files, ProcessorFactory.getHandler(FormatOption.MRR, true, reportStream, ItemDetails itemDetails = ItemDetails.fromValues(pdfFilename);
processorConfig.getValidatorConfig().isRecordPasses())); inputStream.mark(Integer.MAX_VALUE);
pdfReport = reportStream.toString("utf-8").replaceAll("<\\?xml version=\"1\\.0\" encoding=\"utf-8\"\\?>", processor.process(itemDetails, inputStream);
""); pdfReport = reportStream.toString("utf-8").replaceAll(
} catch (final VeraPDFException e) { "<\\?xml version=\"1\\.0\" encoding=\"utf-8\"\\?>",
final ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(6) ""
.setPart(EPart.pdf); );
final StringWriter sw = new StringWriter(); inputStream.reset();
final PrintWriter pw = new PrintWriter(sw); } catch (final Exception excep) {
e.printStackTrace(pw);
vri.setStacktrace(sw.toString());
context.addResultItem(vri);
} catch (final IOException excep) {
context.addResultItem(new ValidationResultItem(ESeverity.exception, excep.getMessage()).setSection(7) context.addResultItem(new ValidationResultItem(ESeverity.exception, excep.getMessage()).setSection(7)
.setPart(EPart.pdf).setStacktrace(excep.getStackTrace().toString())); .setPart(EPart.pdf).setStacktrace(excep.getStackTrace().toString()));
} }
// step 2 validate XMP // step 2 validate XMP
final ZUGFeRDImporter zi = new ZUGFeRDImporter(pdfFilename); final ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream);
final String xmp = zi.getXMP(); final String xmp = zi.getXMP();
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -137,7 +133,7 @@ public class PDFValidator extends Validator {
if (xmp.length() == 0) { if (xmp.length() == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Invalid XMP Metadata not found") context.addResultItem(new ValidationResultItem(ESeverity.error, "Invalid XMP Metadata not found")
.setSection(17).setPart(EPart.pdf)); .setSection(17).setPart(EPart.pdf));
} }
/* /*
* checking for sth like <zf:ConformanceLevel>EXTENDED</zf:ConformanceLevel> * checking for sth like <zf:ConformanceLevel>EXTENDED</zf:ConformanceLevel>
@@ -160,26 +156,28 @@ public class PDFValidator extends Validator {
// get the first element // get the first element
XPathExpression xpr = xpath.compile( XPathExpression xpr = xpath.compile(
"//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel"); "//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel");
NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
if (nodes.getLength() == 0) { if (nodes.getLength() == 0) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found") new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found")
.setSection(11).setPart(EPart.pdf)); .setSection(11).setPart(EPart.pdf));
} }
boolean conformanceLevelValid=false; boolean conformanceLevelValid = false;
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
final String[] valueArray = { "BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG" }; final String[] valueArray = {"BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG"};
if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
conformanceLevelValid=true; conformanceLevelValid = true;
} }
} }
if (!conformanceLevelValid) { if (!conformanceLevelValid) {
context.addResultItem(new ValidationResultItem(ESeverity.error, context.addResultItem(new ValidationResultItem(
"XMP Metadata: ConformanceLevel contains invalid value").setSection(12).setPart(EPart.pdf)); ESeverity.error,
"XMP Metadata: ConformanceLevel contains invalid value"
).setSection(12).setPart(EPart.pdf));
} }
xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType"); xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType");
@@ -187,43 +185,47 @@ public class PDFValidator extends Validator {
if (nodes.getLength() == 0) { if (nodes.getLength() == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found") context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found")
.setSection(13).setPart(EPart.pdf)); .setSection(13).setPart(EPart.pdf));
} }
boolean documentTypeValid=false; boolean documentTypeValid = false;
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
if (nodes.item(i).getTextContent().equals("INVOICE")||nodes.item(i).getTextContent().equals("ORDER")||nodes.item(i).getTextContent().equals("ORDER_RESPONSE")||nodes.item(i).getTextContent().equals("ORDER_CHANGE")) { if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER")
documentTypeValid=true; || nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent()
.equals("ORDER_CHANGE")) {
documentTypeValid = true;
} }
} }
if (!documentTypeValid) { if (!documentTypeValid) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid") new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid")
.setSection(14).setPart(EPart.pdf)); .setSection(14).setPart(EPart.pdf));
} }
xpr = xpath.compile( xpr = xpath.compile(
"//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName"); "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName");
nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
if (nodes.getLength() == 0) { if (nodes.getLength() == 0) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found") new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found")
.setSection(21).setPart(EPart.pdf)); .setSection(21).setPart(EPart.pdf));
} }
boolean documentFilenameValid=false; boolean documentFilenameValid = false;
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
final String[] valueArray = { "factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml" , "order-x.xml" }; final String[] valueArray = {"factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml", "order-x.xml"};
if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
documentFilenameValid=true; documentFilenameValid = true;
} }
// e.g. ZUGFeRD-invoice.xml // e.g. ZUGFeRD-invoice.xml
} }
if (!documentFilenameValid) { if (!documentFilenameValid) {
context.addResultItem(new ValidationResultItem(ESeverity.error, context.addResultItem(new ValidationResultItem(
"XMP Metadata: DocumentFileName contains invalid value").setSection(19).setPart(EPart.pdf)); ESeverity.error,
"XMP Metadata: DocumentFileName contains invalid value"
).setSection(19).setPart(EPart.pdf));
} }
xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version"); xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version");
nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
@@ -234,20 +236,20 @@ public class PDFValidator extends Validator {
// print the text content of each child // print the text content of each child
if (nodes.getLength() == 0) { if (nodes.getLength() == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found") context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found")
.setSection(15).setPart(EPart.pdf)); .setSection(15).setPart(EPart.pdf));
} }
boolean versionValid=false; boolean versionValid = false;
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
final String[] valueArray = { "1.0", "2p0", "1.2", "2.0" , "2.1" }; //1.2, 2.0 and 2.1 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1 final String[] valueArray = {"1.0", "2p0", "1.2", "2.0", "2.1"}; //1.2, 2.0 and 2.1 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1
if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
versionValid=true; versionValid = true;
} // e.g. 1.0 } // e.g. 1.0
} }
if (!versionValid) { if (!versionValid) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value") new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value")
.setSection(16).setPart(EPart.pdf)); .setSection(16).setPart(EPart.pdf));
} }
@@ -272,19 +274,19 @@ public class PDFValidator extends Validator {
final byte[] pdfMachineSignature = "pdfMachine from Broadgun Software".getBytes("UTF-8"); final byte[] pdfMachineSignature = "pdfMachine from Broadgun Software".getBytes("UTF-8");
final byte[] ghostscriptSignature = "%%Invocation:".getBytes("UTF-8"); final byte[] ghostscriptSignature = "%%Invocation:".getBytes("UTF-8");
if (searcher.indexOf(file, symtraxSignature) != -1) { if (ByteArraySearcher.contains(fileContents, symtraxSignature)) {
Signature = "Symtrax"; Signature = "Symtrax";
} else if (searcher.indexOf(file, mustangSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, mustangSignature)) {
Signature = "Mustang"; Signature = "Mustang";
} else if (searcher.indexOf(file, facturxpythonSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, facturxpythonSignature)) {
Signature = "Factur/X Python"; Signature = "Factur/X Python";
} else if (searcher.indexOf(file, intarsysSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, intarsysSignature)) {
Signature = "Intarsys"; Signature = "Intarsys";
} else if (searcher.indexOf(file, konikSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, konikSignature)) {
Signature = "Konik"; Signature = "Konik";
} else if (searcher.indexOf(file, pdfMachineSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, pdfMachineSignature)) {
Signature = "pdfMachine"; Signature = "pdfMachine";
} else if (searcher.indexOf(file, ghostscriptSignature) != -1) { } else if (ByteArraySearcher.contains(fileContents, ghostscriptSignature)) {
Signature = "Ghostscript"; Signature = "Ghostscript";
} }
@@ -295,14 +297,14 @@ public class PDFValidator extends Validator {
} }
// step 4:validate additional data // step 4:validate additional data
final HashMap<String, byte[]> additionalData=zi.getAdditionalData(); final HashMap<String, byte[]> additionalData = zi.getAdditionalData();
for (final String filename : additionalData.keySet()) { for (final String filename : additionalData.keySet()) {
// validating xml in byte[] additionalData.get(filename) // validating xml in byte[] additionalData.get(filename)
LOGGER.info("validating additionalData " + filename); LOGGER.info("validating additionalData " + filename);
validateSchema(additionalData.get(filename), "ad/basic/additional_data_base_schema.xsd", 2, EPart.pdf); validateSchema(additionalData.get(filename), "ad/basic/additional_data_base_schema.xsd", 2, EPart.pdf);
} }
//end //end
final long endTime = Calendar.getInstance().getTimeInMillis(); final long endTime = Calendar.getInstance().getTimeInMillis();
@@ -311,22 +313,26 @@ public class PDFValidator extends Validator {
} }
if (!pdfReport.contains("PDF/A-3")) { if (!pdfReport.contains("PDF/A-3")) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.error, "Not a PDF/A-3").setSection(23).setPart(EPart.pdf)); new ValidationResultItem(ESeverity.error, "Not a PDF/A-3").setSection(23).setPart(EPart.pdf));
} }
context.addCustomXML(pdfReport + "<info><signature>" context.addCustomXML(pdfReport + "<info><signature>"
+ ((context.getSignature() != null) ? context.getSignature() : "unknown") + ((context.getSignature() != null) ? context.getSignature() : "unknown")
+ "</signature><duration unit=\"ms\">" + (endTime - startPDFTime) + "</duration></info>"); + "</signature><duration unit=\"ms\">" + (endTime - startPDFTime) + "</duration></info>");
} }
@Override @Override
public void setFilename(String filename) throws IrrecoverableValidationError { public void setFilename(String filename) throws IrrecoverableValidationError {
this.pdfFilename = filename; this.pdfFilename = filename;
} }
public void setFileContents(byte[] fileContents) {
this.fileContents = fileContents;
}
public String getRawXML() { public String getRawXML() {
return zfXML; return zfXML;

View File

@@ -30,7 +30,7 @@ import org.xml.sax.InputSource;
//abstract class //abstract class
public class ZUGFeRDValidator { public class ZUGFeRDValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDValidator.class.getCanonicalName()); // log private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDValidator.class.getCanonicalName()); // log
// output // output
protected ValidationContext context = new ValidationContext(LOGGER); protected ValidationContext context = new ValidationContext(LOGGER);
protected String sha1Checksum; protected String sha1Checksum;
protected boolean pdfValidity; protected boolean pdfValidity;
@@ -40,7 +40,7 @@ public class ZUGFeRDValidator {
protected boolean disableNotices = false; protected boolean disableNotices = false;
protected String Signature; protected String Signature;
protected boolean wasCompletelyValid = false; protected boolean wasCompletelyValid = false;
protected String logAppend=null; protected String logAppend = null;
/*** /***
* within the validation it turned out something in the options was wrong, e.g. * within the validation it turned out something in the options was wrong, e.g.
@@ -59,7 +59,7 @@ public class ZUGFeRDValidator {
/*** /***
* in case the result was not valid the error code of the app will be set to -1 * in case the result was not valid the error code of the app will be set to -1
* *
* @return true if both xml and pdf were valid (contained no errors, notices are ignored) * @return true if both xml and pdf were valid (contained no errors, notices are ignored)
*/ */
public boolean wasCompletelyValid() { public boolean wasCompletelyValid() {
@@ -69,7 +69,7 @@ public class ZUGFeRDValidator {
/*** /***
* performs a validation on the file filename * performs a validation on the file filename
* *
* @param filename the complete absolute filename of a PDF or XML * @param filename the complete absolute filename of a PDF or XML
* @return a xml string with the validation result * @return a xml string with the validation result
*/ */
@@ -88,43 +88,46 @@ public class ZUGFeRDValidator {
// ignore // ignore
} }
finalStringResult finalStringResult
.append("<validation filename='" + context.getFilename() + "' datetime='" + isoDF.format(date) + "'>"); .append("<validation filename='" + context.getFilename() + "' datetime='" + isoDF.format(date) + "'>");
boolean isPDF=false; boolean isPDF = false;
byte[] content = null;
try { try {
if (filename == null) { if (filename == null) {
optionsRecognized = false; optionsRecognized = false;
context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Filename not specified").setSection(10) context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Filename not specified").setSection(10)
.setPart(EPart.pdf)); .setPart(EPart.pdf));
} }
PDFValidator pdfv = new PDFValidator(context); PDFValidator pdfv = new PDFValidator(context);
File file = new File(filename); File file = new File(filename);
if (!file.exists()) { if (!file.exists()) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.fatal, "File not found").setSection(1).setPart(EPart.pdf)); new ValidationResultItem(ESeverity.fatal, "File not found").setSection(1).setPart(EPart.pdf));
} else if (file.length() < 32) { } else if (file.length() < 32) {
// with less then 32 bytes it can not even be a proper XML file // with less than 32 bytes it can not even be a proper XML file
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.fatal, "File too small").setSection(5).setPart(EPart.pdf)); new ValidationResultItem(ESeverity.fatal, "File too small").setSection(5).setPart(EPart.pdf));
} else { } else {
BigFileSearcher searcher = new BigFileSearcher(); BigFileSearcher searcher = new BigFileSearcher();
content = Files.readAllBytes(file.toPath());
XMLValidator xv = new XMLValidator(context); XMLValidator xv = new XMLValidator(context);
if (disableNotices) { if (disableNotices) {
xv.disableNotices(); xv.disableNotices();
} }
byte[] pdfSignature = { '%', 'P', 'D', 'F' }; byte[] pdfSignature = {'%', 'P', 'D', 'F'};
isPDF = searcher.indexOf(file, pdfSignature) == 0; isPDF = searcher.indexOf(file, pdfSignature) == 0;
if (isPDF) { if (isPDF) {
pdfv.setFilename(filename); pdfv.setFilename(filename);
pdfv.setFileContents(content);
optionsRecognized = true; optionsRecognized = true;
try { try {
if (!file.exists()) { if (!file.exists()) {
context.addResultItem( context.addResultItem(
new ValidationResultItem(ESeverity.exception, "File " + filename + " not found") new ValidationResultItem(ESeverity.exception, "File " + filename + " not found")
.setSection(1)); .setSection(1));
} }
} catch (IrrecoverableValidationError irx) { } catch (IrrecoverableValidationError irx) {
// @todo log // @todo log
@@ -135,24 +138,12 @@ public class ZUGFeRDValidator {
try { try {
pdfv.validate(); pdfv.validate();
sha1Checksum = calcSHA1(file); sha1Checksum = calcSHA1(new FileInputStream(file));
// Validate PDF // Validate PDF
finalStringResult.append(pdfv.getXMLResult()); getPdfValidationResults(finalStringResult, pdfv, xv);
pdfValidity = context.isValid(); } catch (IrrecoverableValidationError | FileNotFoundException irx) {
Signature = context.getSignature();
context.clear();// clear sets valid to true again
if (pdfv.getRawXML() != null) {
xv.setStringContent(pdfv.getRawXML());
displayXMLValidationOutput = true;
} else {
context.addResultItem(
new ValidationResultItem(ESeverity.exception, "XML could not be extracted")
.setSection(17));
}
} catch (IrrecoverableValidationError irx) {
// @todo log // @todo log
} }
@@ -162,21 +153,18 @@ public class ZUGFeRDValidator {
} else { } else {
boolean isXML = false; boolean isXML = false;
try { try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); content = XMLTools.removeBOM(content);
DocumentBuilder db = dbf.newDocumentBuilder(); String s = new String(content, StandardCharsets.UTF_8);
byte[] content=Files.readAllBytes(file.toPath());
content= XMLTools.removeBOM(content);
String s=new String(content, StandardCharsets.UTF_8);
InputSource is = new InputSource(new StringReader(s)); InputSource is = new InputSource(new StringReader(s));
Document doc = db.parse(is); Document doc = db.parse(is);
Element root = doc.getDocumentElement(); Element root = doc.getDocumentElement();
isXML=true;//no exception so far isXML = true;//no exception so far
} } catch (Exception ex) {
catch (Exception ex) {
// probably no xml file, sth like SAXParseException content not allowed in prolog // probably no xml file, sth like SAXParseException content not allowed in prolog
// ignore isXML is already false // ignore isXML is already false
// in the tests, this may error-out anyway // in the tests, this may error-out anyway
@@ -188,7 +176,7 @@ public class ZUGFeRDValidator {
optionsRecognized = true; optionsRecognized = true;
xv.setFilename(filename); xv.setFilename(filename);
if (file.exists()) { if (file.exists()) {
sha1Checksum = calcSHA1(file); sha1Checksum = calcSHA1(Files.newInputStream(file.toPath()));
} }
displayXMLValidationOutput = true; displayXMLValidationOutput = true;
@@ -196,7 +184,7 @@ public class ZUGFeRDValidator {
} else { } else {
optionsRecognized = false; optionsRecognized = false;
context.addResultItem(new ValidationResultItem(ESeverity.exception, context.addResultItem(new ValidationResultItem(ESeverity.exception,
"File does not look like PDF nor XML (contains neither %PDF nor <?xml)").setSection(8)); "File does not look like PDF nor XML (contains neither %PDF nor <?xml)").setSection(8));
} }
} }
@@ -212,14 +200,12 @@ public class ZUGFeRDValidator {
context.clearCustomXML(); context.clearCustomXML();
} }
if ((isPDF)&&(!pdfValidity)) { if ((isPDF) && (!pdfValidity)) {
context.setInvalid(); context.setInvalid();
} }
} }
} } catch (IrrecoverableValidationError | IOException irx) {
catch (IrrecoverableValidationError irx) {
// @todo log // @todo log
} finally { } finally {
finalStringResult.append(context.getXMLResult()); finalStringResult.append(context.getXMLResult());
@@ -227,6 +213,141 @@ public class ZUGFeRDValidator {
} }
return formatOutput(finalStringResult, isPDF);
}
public String validate(InputStream inputStream, String fileNameOfInputStream) {
boolean xmlValidity;
context.clear();
StringBuffer finalStringResult = new StringBuffer();
SimpleDateFormat isoDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
startTime = Calendar.getInstance().getTimeInMillis();
context.setFilename(fileNameOfInputStream);// set filename without path
finalStringResult.append("<validation filename='").append(context.getFilename()).append("' datetime='").append(isoDF.format(date)).append("'>");
boolean isPDF = false;
byte[] content = new byte[0];
try {
if (fileNameOfInputStream == null) {
optionsRecognized = false;
context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Filename not specified").setSection(10)
.setPart(EPart.pdf));
}
PDFValidator pdfv = new PDFValidator(context);
if (inputStream == null) {
context.addResultItem(
new ValidationResultItem(ESeverity.fatal, "File not found").setSection(1).setPart(EPart.pdf));
} else if (inputStream.available() < 32) {
// with less then 32 bytes it can not even be a proper XML file
context.addResultItem(
new ValidationResultItem(ESeverity.fatal, "File too small").setSection(5).setPart(EPart.pdf));
} else {
content = inputStream.readAllBytes();
isPDF = ByteArraySearcher.contains(content, new byte[]{'%', 'P', 'D', 'F'});
XMLValidator xv = new XMLValidator(context);
if (isPDF) {
pdfv.setFilename(fileNameOfInputStream);
pdfv.setFileContents(content);
optionsRecognized = true;
finalStringResult.append("<pdf>");
try {
pdfv.validate();
sha1Checksum = calcSHA1(inputStream);
// Validate PDF
getPdfValidationResults(finalStringResult, pdfv, xv);
} catch (IrrecoverableValidationError irx) {
LOGGER.info(irx.getMessage());
}
finalStringResult.append("</pdf>\n");
context.clearCustomXML();
} else {
boolean isXML = false;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
content = XMLTools.removeBOM(content);
String s = new String(content, StandardCharsets.UTF_8);
InputSource is = new InputSource(new StringReader(s));
Document doc = db.parse(is);
Element root = doc.getDocumentElement();
isXML = true;//no exception so far
} catch (Exception ex) {
LOGGER.info("No XML part provided");
}
if (isXML) {
pdfValidity = true;
optionsRecognized = true;
xv.setFilename(fileNameOfInputStream);
sha1Checksum = calcSHA1(inputStream);
displayXMLValidationOutput = true;
} else {
optionsRecognized = false;
context.addResultItem(new ValidationResultItem(
ESeverity.exception,
"File does not look like PDF nor XML (contains neither %PDF nor <?xml)"
).setSection(8));
}
}
if ((optionsRecognized) && (displayXMLValidationOutput)) {
finalStringResult.append("<xml>");
try {
xv.validate();
} catch (IrrecoverableValidationError irx) {
LOGGER.info("The hell");
}
finalStringResult.append(xv.getXMLResult());
finalStringResult.append("</xml>");
context.clearCustomXML();
}
if ((isPDF) && (!pdfValidity)) {
context.setInvalid();
}
}
} catch (IrrecoverableValidationError | IOException irx) {
LOGGER.info(irx.getMessage());
} finally {
finalStringResult.append(context.getXMLResult());
finalStringResult.append("</validation>");
}
return formatOutput(finalStringResult, isPDF);
}
private void getPdfValidationResults(StringBuffer finalStringResult, PDFValidator pdfv, XMLValidator xv) throws IrrecoverableValidationError {
finalStringResult.append(pdfv.getXMLResult());
pdfValidity = context.isValid();
Signature = context.getSignature();
context.clear();// clear sets valid to true again
if (pdfv.getRawXML() != null) {
xv.setStringContent(pdfv.getRawXML());
displayXMLValidationOutput = true;
} else {
context.addResultItem(
new ValidationResultItem(ESeverity.exception, "XML could not be extracted")
.setSection(17));
}
}
private String formatOutput(StringBuffer finalStringResult, boolean isPDF) {
boolean xmlValidity;
OutputFormat format = OutputFormat.createPrettyPrint(); OutputFormat format = OutputFormat.createPrettyPrint();
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
org.dom4j.Document document = null; org.dom4j.Document document = null;
@@ -245,23 +366,24 @@ public class ZUGFeRDValidator {
xmlValidity = context.isValid(); xmlValidity = context.isValid();
long duration = Calendar.getInstance().getTimeInMillis() - startTime; long duration = Calendar.getInstance().getTimeInMillis() - startTime;
String toBeAppended=""; String toBeAppended = "";
if (logAppend!=null) { if (logAppend != null) {
toBeAppended=logAppend; toBeAppended = logAppend;
} }
String pdfResult="invalid"; String pdfResult = "invalid";
if (!isPDF) { if (!isPDF) {
pdfResult="absent"; pdfResult = "absent";
} else if (pdfValidity) { } else if (pdfValidity) {
pdfResult="valid"; pdfResult = "valid";
} }
LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid") LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid")
+ " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile() + " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile()
+ " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:["+context.getCSVResult()+"] "+toBeAppended); + " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:[" + context.getCSVResult()
+ "] " + toBeAppended);
wasCompletelyValid = ((pdfValidity) && (xmlValidity)); wasCompletelyValid = ((pdfValidity) && (xmlValidity));
return sw.toString(); return sw.toString();
} }
@@ -270,12 +392,13 @@ public class ZUGFeRDValidator {
* don't report notices in validation report * don't report notices in validation report
*/ */
public void disableNotices() { public void disableNotices() {
disableNotices=true; disableNotices = true;
} }
/** /**
* Read the file and calculate the SHA-1 checksum * Read the file and calculate the SHA-1 checksum
* *
* @param file the file to read * @param inputStream the InputStream to read
* @return the hex representation of the SHA-1 using uppercase chars * @return the hex representation of the SHA-1 using uppercase chars
* @throws FileNotFoundException if the file does not exist, is a directory * @throws FileNotFoundException if the file does not exist, is a directory
* rather than a regular file, or for some * rather than a regular file, or for some
@@ -283,25 +406,20 @@ public class ZUGFeRDValidator {
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
* @throws NoSuchAlgorithmException should never happen * @throws NoSuchAlgorithmException should never happen
*/ */
private static String calcSHA1(File file) { private static String calcSHA1(InputStream inputStream) {
MessageDigest sha1 = null; MessageDigest sha1 = null;
try { try {
sha1 = MessageDigest.getInstance("SHA-1"); sha1 = MessageDigest.getInstance("SHA-1");
InputStream input = new FileInputStream(file);
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int len = input.read(buffer); int len = inputStream.read(buffer);
while (len != -1) { while (len != -1) {
sha1.update(buffer, 0, len); sha1.update(buffer, 0, len);
len = input.read(buffer); len = inputStream.read(buffer);
} }
input.close(); inputStream.close();
} catch (FileNotFoundException e) { } catch (IOException | NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage(), e);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage(), e); LOGGER.error(e.getMessage(), e);
} }
if (sha1 == null) { if (sha1 == null) {