From 01c99d2d584070f37d1c92d093da247421cd5f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20St=C3=A4rk?= Date: Tue, 24 Mar 2026 13:31:24 +0100 Subject: [PATCH] parse xml only if xml parseable in invoiceimporter (affecting metrics) --- .../java/org/mustangproject/XMLTools.java | 29 +++++++ .../ZUGFeRD/ZUGFeRDImporter.java | 28 +------ .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 78 ++++++++++++------- 3 files changed, 84 insertions(+), 51 deletions(-) diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index 5905fdae..42e533eb 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -12,6 +12,11 @@ import org.dom4j.io.XMLWriter; import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat; import org.w3c.dom.Node; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + public class XMLTools extends XMLWriter { @Override public String escapeAttributeEntities(String s) { @@ -23,6 +28,30 @@ public class XMLTools extends XMLWriter { return super.escapeElementEntities(s); } + public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //REDHAT + //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(true); + return dbf.newDocumentBuilder(); + + } + + public static String nDigitFormat(BigDecimal value, int scale) { /* * I needed 123.45, locale independent.I tried diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index e05a8652..f99dfff8 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -19,6 +19,8 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; @@ -29,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class); @@ -472,17 +475,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /** - * @return raw XML of the invoice - */ - public String getMeta() { - if (rawXML == null) { - return null; - } - - return new String(rawXML, StandardCharsets.UTF_8); - } - public int getVersion() throws Exception { if (!containsMeta) { @@ -517,20 +509,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /** - * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML - * - * @return true if the invoice contains ZUGFeRD XML - */ - public boolean canParse() { - - // SpecifiedExchangedDocumentContext is in the schema, so a relatively good - // indication if zugferd is present - better than just invoice - final String meta = getMeta(); - return (meta != null) && (meta.length() > 0) && ((meta.contains("SpecifiedExchangedDocumentContext") - /* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); - } - /** * returns an instance of PostalTradeAddress for SellerTradeParty section diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 1297b1e8..5590340c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -311,36 +311,62 @@ public class ZUGFeRDInvoiceImporter { setRawXML(rawXML, true); } - private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - //REDHAT - //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf - dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - //OWASP - //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - // Disable external DTDs as well - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - dbf.setNamespaceAware(true); - final DocumentBuilder builder = dbf.newDocumentBuilder(); + /** + * @return raw XML of the invoice + */ + public String getMeta() { + if (rawXML == null) { + return null; + } + + return new String(rawXML, StandardCharsets.UTF_8); + } + + /** + * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML + * + * @return true if the invoice contains ZUGFeRD XML + */ + public boolean canParse() { + + // SpecifiedExchangedDocumentContext is in the schema, so a relatively good + // indication if zugferd is present - better than just invoice + final String meta = getMeta(); + if ((meta == null) || (meta.length() == 0)) { + return false; + } + final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); /// is.skip(guessBOMSize(is)); - document = builder.parse(is); - if (parseAutomatically) { - try { - importedInvoice = new CalculatedInvoice(); - extractInto(importedInvoice); - } catch (XPathExpressionException e) { - throw new RuntimeException(e); + try { + DocumentBuilder builder = XMLTools.getDocumentBuilder(); + document = builder.parse(is); + + } catch (Exception e) { + return false; + } + return ((meta.contains("SpecifiedExchangedDocumentContext") + /* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); + } + + private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { + + + final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); + /// is.skip(guessBOMSize(is)); + DocumentBuilder builder = XMLTools.getDocumentBuilder(); + if (canParse()) { + document = builder.parse(is); + if (parseAutomatically) { + try { + importedInvoice = new CalculatedInvoice(); + extractInto(importedInvoice); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } } + } }