diff --git a/validator/src/main/java/org/mustangproject/validator/ByteArraySearcher.java b/validator/src/main/java/org/mustangproject/validator/ByteArraySearcher.java new file mode 100644 index 00000000..169d0dd4 --- /dev/null +++ b/validator/src/main/java/org/mustangproject/validator/ByteArraySearcher.java @@ -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; + } +} diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java index 0cd6c4ad..a2c18b16 100644 --- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java @@ -1,8 +1,10 @@ package org.mustangproject.validator; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; @@ -33,14 +35,18 @@ import org.verapdf.features.FeatureFactory; import org.verapdf.metadata.fixer.FixerFactory; import org.verapdf.metadata.fixer.MetadataFixerConfig; import org.verapdf.gf.foundry.VeraGreenfieldFoundryProvider; +import org.verapdf.pdfa.flavours.PDFAFlavour; import org.verapdf.pdfa.validation.validators.ValidatorConfig; import org.verapdf.pdfa.validation.validators.ValidatorFactory; import org.verapdf.processor.BatchProcessor; import org.verapdf.processor.FormatOption; +import org.verapdf.processor.ItemProcessor; import org.verapdf.processor.ProcessorConfig; import org.verapdf.processor.ProcessorFactory; +import org.verapdf.processor.ProcessorResult; import org.verapdf.processor.TaskType; import org.verapdf.processor.plugins.PluginsCollectionConfig; +import org.verapdf.processor.reports.ItemDetails; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -50,15 +56,17 @@ public class PDFValidator extends Validator { public PDFValidator(ValidationContext ctx) { super(ctx); - // TODO Auto-generated constructor stub } private static final Logger LOGGER = LoggerFactory.getLogger(PDFValidator.class.getCanonicalName()); // log output - // is + private static final PDFAFlavour[] PDF_A_3_FLAVOURS = {PDFAFlavour.PDFA_3_A, PDFAFlavour.PDFA_3_A, PDFAFlavour.PDFA_3_A}; private String pdfFilename; + private byte[] fileContents; + private String pdfReport; + private ProcessorResult processorResult = null; private String Signature; @@ -69,17 +77,13 @@ public class PDFValidator extends Validator { } @Override - public void validate() throws IrrecoverableValidationError { + public void validate() throws IrrecoverableValidationError { zfXML = null; - final File file = new File(pdfFilename); // file existence must have been checked before - final BigFileSearcher searcher = new BigFileSearcher(); - - final byte[] pdfSignature = { '%', 'P', 'D', 'F' }; - if (searcher.indexOf(file, pdfSignature) != 0) { + if (!ByteArraySearcher.contains(fileContents, new byte[]{'%', 'P', 'D', 'F'})) { 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 +107,29 @@ public class PDFValidator extends Validator { // tasks.add(TaskType.FIX_METADATA); // Creating processor config final ProcessorConfig processorConfig = ProcessorFactory.fromValues(validatorConfig, featureConfig, pluginsConfig, - fixerConfig, tasks); + fixerConfig, tasks + ); // Creating processor and output stream. 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 - final List files = new ArrayList<>(); - files.add(new File(pdfFilename)); // starting the processor - processor.process(files, ProcessorFactory.getHandler(FormatOption.MRR, true, reportStream, - processorConfig.getValidatorConfig().isRecordPasses())); - pdfReport = reportStream.toString("utf-8").replaceAll("<\\?xml version=\"1\\.0\" encoding=\"utf-8\"\\?>", - ""); - } catch (final VeraPDFException e) { - final ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(6) - .setPart(EPart.pdf); - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - vri.setStacktrace(sw.toString()); - context.addResultItem(vri); - } catch (final IOException excep) { + ItemDetails itemDetails = ItemDetails.fromValues(pdfFilename); + inputStream.mark(Integer.MAX_VALUE); + processorResult = processor.process(itemDetails, inputStream); + pdfReport = processorResult.getValidationResult().toString().replaceAll( + "<\\?xml version=\"1\\.0\" encoding=\"utf-8\"\\?>", + "" + ); + inputStream.reset(); + } catch (final Exception excep) { 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 - final ZUGFeRDImporter zi = new ZUGFeRDImporter(pdfFilename); + final ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream); final String xmp = zi.getXMP(); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -137,7 +137,7 @@ public class PDFValidator extends Validator { if (xmp.length() == 0) { 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 EXTENDED @@ -160,26 +160,28 @@ public class PDFValidator extends Validator { // get the first element 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); if (nodes.getLength() == 0) { context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found") - .setSection(11).setPart(EPart.pdf)); + new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found") + .setSection(11).setPart(EPart.pdf)); } - - boolean conformanceLevelValid=false; + + boolean conformanceLevelValid = false; 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())) { - conformanceLevelValid=true; + conformanceLevelValid = true; } } if (!conformanceLevelValid) { - context.addResultItem(new ValidationResultItem(ESeverity.error, - "XMP Metadata: ConformanceLevel contains invalid value").setSection(12).setPart(EPart.pdf)); + context.addResultItem(new ValidationResultItem( + ESeverity.error, + "XMP Metadata: ConformanceLevel contains invalid value" + ).setSection(12).setPart(EPart.pdf)); } xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType"); @@ -187,43 +189,47 @@ public class PDFValidator extends Validator { if (nodes.getLength() == 0) { 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++) { - 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")) { - documentTypeValid=true; + 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")) { + documentTypeValid = true; } } if (!documentTypeValid) { context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid") - .setSection(14).setPart(EPart.pdf)); + new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid") + .setSection(14).setPart(EPart.pdf)); } xpr = xpath.compile( - "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName"); + "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName"); nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); if (nodes.getLength() == 0) { context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found") - .setSection(21).setPart(EPart.pdf)); + new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found") + .setSection(21).setPart(EPart.pdf)); } - boolean documentFilenameValid=false; + boolean documentFilenameValid = false; 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())) { - documentFilenameValid=true; + documentFilenameValid = true; } // e.g. ZUGFeRD-invoice.xml } if (!documentFilenameValid) { - context.addResultItem(new ValidationResultItem(ESeverity.error, - "XMP Metadata: DocumentFileName contains invalid value").setSection(19).setPart(EPart.pdf)); + context.addResultItem(new ValidationResultItem( + ESeverity.error, + "XMP Metadata: DocumentFileName contains invalid value" + ).setSection(19).setPart(EPart.pdf)); } xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version"); nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); @@ -234,30 +240,23 @@ public class PDFValidator extends Validator { // print the text content of each child if (nodes.getLength() == 0) { 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++) { - 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())) { - versionValid=true; + versionValid = true; } // e.g. 1.0 } if (!versionValid) { context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value") - .setSection(16).setPart(EPart.pdf)); + new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value") + .setSection(16).setPart(EPart.pdf)); } - - } catch (final SAXException e) { - LOGGER.error(e.getMessage(), e); - } catch (final IOException e) { - LOGGER.error(e.getMessage(), e); - } catch (final ParserConfigurationException e) { - LOGGER.error(e.getMessage(), e); - } catch (final XPathExpressionException e) { + } catch (final SAXException | IOException | ParserConfigurationException | XPathExpressionException e) { LOGGER.error(e.getMessage(), e); } zfXML = zi.getUTF8(); @@ -272,19 +271,19 @@ public class PDFValidator extends Validator { final byte[] pdfMachineSignature = "pdfMachine from Broadgun Software".getBytes("UTF-8"); final byte[] ghostscriptSignature = "%%Invocation:".getBytes("UTF-8"); - if (searcher.indexOf(file, symtraxSignature) != -1) { + if (ByteArraySearcher.contains(fileContents, symtraxSignature)) { Signature = "Symtrax"; - } else if (searcher.indexOf(file, mustangSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, mustangSignature)) { Signature = "Mustang"; - } else if (searcher.indexOf(file, facturxpythonSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, facturxpythonSignature)) { Signature = "Factur/X Python"; - } else if (searcher.indexOf(file, intarsysSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, intarsysSignature)) { Signature = "Intarsys"; - } else if (searcher.indexOf(file, konikSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, konikSignature)) { Signature = "Konik"; - } else if (searcher.indexOf(file, pdfMachineSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, pdfMachineSignature)) { Signature = "pdfMachine"; - } else if (searcher.indexOf(file, ghostscriptSignature) != -1) { + } else if (ByteArraySearcher.contains(fileContents, ghostscriptSignature)) { Signature = "Ghostscript"; } @@ -295,38 +294,43 @@ public class PDFValidator extends Validator { } // step 4:validate additional data - final HashMap additionalData=zi.getAdditionalData(); + final HashMap additionalData = zi.getAdditionalData(); for (final String filename : additionalData.keySet()) { // validating xml in byte[] additionalData.get(filename) LOGGER.info("validating additionalData " + filename); validateSchema(additionalData.get(filename), "ad/basic/additional_data_base_schema.xsd", 2, EPart.pdf); } - - + + //end final long endTime = Calendar.getInstance().getTimeInMillis(); - if (!pdfReport.contains("validationReports compliant=\"1\"")) { + if (!processorResult.getValidationResult().isCompliant()) { context.setInvalid(); } - if (!pdfReport.contains("PDF/A-3")) { + if (Arrays.stream(PDF_A_3_FLAVOURS) + .anyMatch(pdfaFlavour -> processorResult.getValidationResult().getPDFAFlavour().equals(pdfaFlavour))) { 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 + "" - + ((context.getSignature() != null) ? context.getSignature() : "unknown") - + "" + (endTime - startPDFTime) + ""); + + ((context.getSignature() != null) ? context.getSignature() : "unknown") + + "" + (endTime - startPDFTime) + ""); } - + @Override public void setFilename(String filename) throws IrrecoverableValidationError { this.pdfFilename = filename; } + public void setFileContents(byte[] fileContents) { + this.fileContents = fileContents; + } + public String getRawXML() { return zfXML; diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index f72aff90..981d732f 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -15,6 +15,7 @@ import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.commons.io.IOUtils; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.io.OutputFormat; @@ -30,7 +31,7 @@ import org.xml.sax.InputSource; //abstract class public class ZUGFeRDValidator { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDValidator.class.getCanonicalName()); // log - // output + // output protected ValidationContext context = new ValidationContext(LOGGER); protected String sha1Checksum; protected boolean pdfValidity; @@ -40,7 +41,7 @@ public class ZUGFeRDValidator { protected boolean disableNotices = false; protected String Signature; 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. @@ -59,7 +60,7 @@ public class ZUGFeRDValidator { /*** * 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) */ public boolean wasCompletelyValid() { @@ -69,7 +70,7 @@ public class ZUGFeRDValidator { /*** * performs a validation on the file filename - * + * * @param filename the complete absolute filename of a PDF or XML * @return a xml string with the validation result */ @@ -88,43 +89,46 @@ public class ZUGFeRDValidator { // ignore } finalStringResult - .append(""); + .append(""); - boolean isPDF=false; + boolean isPDF = false; + byte[] content = null; try { if (filename == null) { optionsRecognized = false; context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Filename not specified").setSection(10) - .setPart(EPart.pdf)); + .setPart(EPart.pdf)); } PDFValidator pdfv = new PDFValidator(context); File file = new File(filename); if (!file.exists()) { 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) { - // 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( - 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 { BigFileSearcher searcher = new BigFileSearcher(); + content = Files.readAllBytes(file.toPath()); XMLValidator xv = new XMLValidator(context); if (disableNotices) { xv.disableNotices(); } - byte[] pdfSignature = { '%', 'P', 'D', 'F' }; + byte[] pdfSignature = {'%', 'P', 'D', 'F'}; isPDF = searcher.indexOf(file, pdfSignature) == 0; if (isPDF) { pdfv.setFilename(filename); + pdfv.setFileContents(content); optionsRecognized = true; try { if (!file.exists()) { context.addResultItem( - new ValidationResultItem(ESeverity.exception, "File " + filename + " not found") - .setSection(1)); + new ValidationResultItem(ESeverity.exception, "File " + filename + " not found") + .setSection(1)); } } catch (IrrecoverableValidationError irx) { // @todo log @@ -135,24 +139,12 @@ public class ZUGFeRDValidator { try { pdfv.validate(); - sha1Checksum = calcSHA1(file); + sha1Checksum = calcSHA1(new FileInputStream(file)); // Validate PDF - 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)); - } - } catch (IrrecoverableValidationError irx) { + getPdfValidationResults(finalStringResult, pdfv, xv); + } catch (IrrecoverableValidationError | FileNotFoundException irx) { // @todo log } @@ -162,21 +154,18 @@ public class ZUGFeRDValidator { } else { boolean isXML = false; try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - - byte[] content=Files.readAllBytes(file.toPath()); - content= XMLTools.removeBOM(content); - String s=new String(content, StandardCharsets.UTF_8); + 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) { + Document doc = db.parse(is); + + Element root = doc.getDocumentElement(); + isXML = true;//no exception so far + + } catch (Exception ex) { // probably no xml file, sth like SAXParseException content not allowed in prolog // ignore isXML is already false // in the tests, this may error-out anyway @@ -188,7 +177,7 @@ public class ZUGFeRDValidator { optionsRecognized = true; xv.setFilename(filename); if (file.exists()) { - sha1Checksum = calcSHA1(file); + sha1Checksum = calcSHA1(Files.newInputStream(file.toPath())); } displayXMLValidationOutput = true; @@ -196,7 +185,7 @@ public class ZUGFeRDValidator { } else { optionsRecognized = false; context.addResultItem(new ValidationResultItem(ESeverity.exception, - "File does not look like PDF nor XML (contains neither %PDF nor "); + + 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 = IOUtils.toByteArray(inputStream); + 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(""); + try { + pdfv.validate(); + + sha1Checksum = calcSHA1(inputStream); + + // Validate PDF + + getPdfValidationResults(finalStringResult, pdfv, xv); + } catch (IrrecoverableValidationError irx) { + LOGGER.info(irx.getMessage()); + } + + finalStringResult.append("\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 "); + try { + xv.validate(); + } catch (IrrecoverableValidationError irx) { + LOGGER.info("The hell"); + } + finalStringResult.append(xv.getXMLResult()); + finalStringResult.append(""); + context.clearCustomXML(); + } + + if ((isPDF) && (!pdfValidity)) { + context.setInvalid(); + } + + } + } catch (IrrecoverableValidationError | IOException irx) { + LOGGER.info(irx.getMessage()); + } finally { + finalStringResult.append(context.getXMLResult()); + finalStringResult.append(""); + + } + + 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(); StringWriter sw = new StringWriter(); org.dom4j.Document document = null; @@ -245,23 +367,24 @@ public class ZUGFeRDValidator { xmlValidity = context.isValid(); long duration = Calendar.getInstance().getTimeInMillis() - startTime; - String toBeAppended=""; - if (logAppend!=null) { - toBeAppended=logAppend; + String toBeAppended = ""; + if (logAppend != null) { + toBeAppended = logAppend; } - String pdfResult="invalid"; + String pdfResult = "invalid"; if (!isPDF) { - pdfResult="absent"; + pdfResult = "absent"; } else if (pdfValidity) { - pdfResult="valid"; + pdfResult = "valid"; } LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid") - + " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile() - + " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:["+context.getCSVResult()+"] "+toBeAppended); + + " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile() + + " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:[" + context.getCSVResult() + + "] " + toBeAppended); wasCompletelyValid = ((pdfValidity) && (xmlValidity)); return sw.toString(); } @@ -270,12 +393,13 @@ public class ZUGFeRDValidator { * don't report notices in validation report */ public void disableNotices() { - disableNotices=true; + disableNotices = true; } + /** * 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 * @throws FileNotFoundException if the file does not exist, is a directory * rather than a regular file, or for some @@ -283,25 +407,20 @@ public class ZUGFeRDValidator { * @throws IOException if an I/O error occurs * @throws NoSuchAlgorithmException should never happen */ - private static String calcSHA1(File file) { + private static String calcSHA1(InputStream inputStream) { MessageDigest sha1 = null; try { sha1 = MessageDigest.getInstance("SHA-1"); - InputStream input = new FileInputStream(file); byte[] buffer = new byte[8192]; - int len = input.read(buffer); + int len = inputStream.read(buffer); while (len != -1) { sha1.update(buffer, 0, len); - len = input.read(buffer); + len = inputStream.read(buffer); } - input.close(); - } catch (FileNotFoundException e) { - LOGGER.error(e.getMessage(), e); - } catch (IOException e) { - LOGGER.error(e.getMessage(), e); - } catch (NoSuchAlgorithmException e) { + inputStream.close(); + } catch (IOException | NoSuchAlgorithmException e) { LOGGER.error(e.getMessage(), e); } if (sha1 == null) { diff --git a/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java index f267b094..5084dddd 100644 --- a/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java @@ -55,15 +55,17 @@ public class PDFValidatorTest extends ResourceCase { try { - File tempFile = getResourceAsFile("XMLinvalidV2PDF.pdf");// need a more invalid file here + byte [] contents = getResourceAsByteArray("XMLinvalidV2PDF.pdf");// need a more invalid file here - pv.setFilename(tempFile.getAbsolutePath()); + pv.setFilename("XMLinvalidV2PDF.pdf"); + pv.setFileContents(contents); pv.validate(); // assertEquals("", pv.getXMLResult()); // - tempFile = getResourceAsFile("Facture_F20180027.pdf"); - pv.setFilename(tempFile.getAbsolutePath()); + contents = getResourceAsByteArray("Facture_F20180027.pdf"); + pv.setFilename("Facture_F20180027.pdf"); + pv.setFileContents(contents); pv.validate(); String actual = pv.getXMLResult(); assertEquals(true, actual.contains("summary status=\"valid")); @@ -74,10 +76,10 @@ public class PDFValidatorTest extends ResourceCase { xv.validate(); actual = vc.getXMLResult(); - assertEquals(true, actual.contains("validationReport profileName=\"PDF/A-3")); - assertEquals(true, actual.contains("batchSummary totalJobs=\"1\" failedToParse=\"0\" encrypted=\"0\"")); + assertEquals(true, actual.contains("flavour=3u")); + assertEquals(true, actual.contains("flavour=3b")); assertEquals(true, - actual.contains("validationReports compliant=\"1\" nonCompliant=\"0\" failedJobs=\"0\">")); + actual.contains("isCompliant=true")); // test some xml // assertEquals(true, actual.contains("2")); // valid one - tempFile = getResourceAsFile("validV2PDF.pdf"); + contents = getResourceAsByteArray("validV2PDF.pdf"); - pv.setFilename(tempFile.getAbsolutePath()); + pv.setFilename("validV2PDF.pdf"); + pv.setFileContents(contents); vc.clear(); pv.validate(); actual = pv.getXMLResult(); - assertEquals(true, actual.contains("validationReport profileName=\"PDF/A-3")); - assertEquals(true, actual.contains("batchSummary totalJobs=\"1\" failedToParse=\"0\" encrypted=\"0\"")); - assertEquals(true, - actual.contains("validationReports compliant=\"1\" nonCompliant=\"0\" failedJobs=\"0\">")); + assertEquals(true, actual.contains("flavour=3u")); + assertEquals(true, actual.contains("summary status=\"valid")); + assertEquals(false, actual.contains("summary status=\"invalid")); assertEquals(false, actual.contains("XMP Metadata: ConformanceLevel contains invalid value")); - tempFile = getResourceAsFile("attributeBasedXMP_zugferd_2p0_EN16931_Einfach.pdf"); + contents = getResourceAsByteArray("attributeBasedXMP_zugferd_2p0_EN16931_Einfach.pdf"); - pv.setFilename(tempFile.getAbsolutePath()); + pv.setFilename("attributeBasedXMP_zugferd_2p0_EN16931_Einfach.pdf"); + pv.setFileContents(contents); vc.clear(); pv.validate(); actual = pv.getXMLResult(); diff --git a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java index 5810700f..27ff2755 100644 --- a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java +++ b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java @@ -5,6 +5,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,4 +38,18 @@ public class ResourceCase extends TestCase { } } + public static byte[] getResourceAsByteArray(String resourcePath) { + try { + InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath); + if (in == null) { + return null; + } + return IOUtils.toByteArray(in); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + return null; + } + } + + }