diff --git a/History.md b/History.md index e8f81cdd..d91af9a5 100644 --- a/History.md +++ b/History.md @@ -1,22 +1,32 @@ -#558 -#686 -#739 +2.16.3 +======= +2025-03-03 +- #558 ZUGFeRDInvoiceImporter does not read BankDetails.accountName +- #686 Item: add BillingSpecifiedPeriod +- #739 also parse invoiceperiod from ubl +- #745 be able to specify legalorganisation id without schema +- #747 correct profile detection +- #710 Validation Error due to empty elements +- #712 Correct bracket setting on condition for output of allowance reason. +- #725 Unable to perform XML-oriented attacks +- #685 Security Issue: XXE Vulnerability in ZUGFeRDInvoiceImporter (PR #725) +- #761 Allow to set item allowance/charges from JSON 2.16.2 ======= 2025-02-04 --#705 specifiedLogisticsCharge is not imported --#707 invoiceimporter may fail if certain values are not set --#708 embedded files cannot be determined --#709 ZUGFeRDInvoiceImporter ignored "first" embedded file in list of pdf attachments --#607 Enable flexible PaymentReference and a DocumentName. --#649 Reuse toPDF method to work without any dependencies to the file system --#650 Add net.sf.offo:fop-hyph --#665 Fix #632: Return ubl_creditnote as Standard for CreditNotes --#684 Optimize validation-report to pdf functionality --#703 Fill TaxExemptionReason during InvoiceImport. --#701 Ensure Base64 decoding can handle newlines when decoding a FileAttachment --#691 Fix current check failures. +- #705 specifiedLogisticsCharge is not imported +- #707 invoiceimporter may fail if certain values are not set +- #708 embedded files cannot be determined +- #709 ZUGFeRDInvoiceImporter ignored "first" embedded file in list of pdf attachments +- #607 Enable flexible PaymentReference and a DocumentName. +- #649 Reuse toPDF method to work without any dependencies to the file system +- #650 Add net.sf.offo:fop-hyph +- #665 Fix #632: Return ubl_creditnote as Standard for CreditNotes +- #684 Optimize validation-report to pdf functionality +- #703 Fill TaxExemptionReason during InvoiceImport. +- #701 Ensure Base64 decoding can handle newlines when decoding a FileAttachment +- #691 Fix current check failures. 2.16.1 ======= diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index d7c156e4..e040777d 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT diff --git a/library/pom.xml b/library/pom.xml index d3ed2ca7..9862a1d5 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT 4.0.0 org.mustangproject library - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT jar Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII) FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 456e465a..97e368ab 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -369,6 +369,29 @@ public class Item implements IZUGFeRDExportableItem { return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]); } + /*** + * jackson convenience method + */ + public void setItemAllowances(ArrayList theAllowances) { + if (theAllowances!=null) { + Allowances.clear(); + for (Allowance theAllowance : theAllowances) { + Allowances.add(theAllowance); + } + } + } + /*** + * jackson convenience method + */ + public void setItemCharges(ArrayList theCharges) { + if (theCharges!=null) { + Charges.clear(); + for (Charge theCharge : theCharges) { + Charges.add(theCharge); + } + } + } + @Override public IZUGFeRDAllowanceCharge[] getItemCharges() { if (Charges.isEmpty()) { diff --git a/library/src/main/java/org/mustangproject/LegalOrganisation.java b/library/src/main/java/org/mustangproject/LegalOrganisation.java index 705420c2..d5b6d42f 100644 --- a/library/src/main/java/org/mustangproject/LegalOrganisation.java +++ b/library/src/main/java/org/mustangproject/LegalOrganisation.java @@ -23,6 +23,10 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation { this.schemedID = new SchemedID(scheme, ID); } + public LegalOrganisation(String ID) { + this.schemedID = new SchemedID(null, ID); + } + public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) { this.schemedID = schemedID; this.tradingBusinessName=tradingBusinessName; diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index 3fd71c63..e5e297f1 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -130,6 +130,9 @@ public class XMLTools extends XMLWriter { */ public static Date tryDate(String toParse) { SimpleDateFormat formatter = null; + if (toParse==null) { + return null; + } if (toParse.contains("-")) { // from ubl formatter = new SimpleDateFormat("yyyy-MM-dd"); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java index 686daa7c..d6397e22 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java @@ -1,5 +1,13 @@ package org.mustangproject.ZUGFeRD; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; import org.apache.fop.apps.*; import org.apache.fop.apps.io.ResourceResolverFactory; import org.apache.fop.configuration.Configuration; @@ -14,7 +22,7 @@ import javax.xml.transform.*; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import java.io.*; + import java.nio.charset.StandardCharsets; public class ValidationLogVisualizer { @@ -70,7 +78,7 @@ public class ValidationLogVisualizer { return baos.toString(StandardCharsets.UTF_8); } - public void toPDF(String xmlLogfileContent, String pdfFilename) { + public byte[] createPDFBytes(String xmlLogfileContent) { // the writing part @@ -111,7 +119,8 @@ public class ValidationLogVisualizer { // Step 2: Set up output stream. // Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams). - try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStream out = new BufferedOutputStream(baos)) { // Step 3: Construct fop with desired output format Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out); @@ -133,6 +142,20 @@ public class ValidationLogVisualizer { } catch (FOPException | IOException | TransformerException e) { LOGGER.error("Failed to create PDF", e); } + return baos.toByteArray(); + } + + public byte[] toPDF(String xmlLogfileContent) { + return createPDFBytes(xmlLogfileContent); + } + + public void toPDF(String xmlLogfileContent, String pdfFilename) { + byte[] pdfData = createPDFBytes(xmlLogfileContent); + try (FileOutputStream fos = new FileOutputStream(pdfFilename)) { + fos.write(pdfData); + } catch (IOException e) { + LOGGER.error("Failed to write PDF to file", e); + } } private static class ClasspathResourceURIResolver implements URIResolver { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 6958bb61..79ea800e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -149,7 +149,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (profile == Profiles.getByName("Minimum")) { xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; } else { - xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; + String schemeAttribute=""; + if ((party.getLegalOrganisation().getSchemedID().getScheme()!=null)&&(party.getLegalOrganisation().getSchemedID().getScheme().length()>0)) { + schemeAttribute="schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme())+"\""; + + } + xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; } } if (party.getLegalOrganisation().getTradingBusinessName() != null) { @@ -277,7 +282,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } String reason = ""; - if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung")) || profile == Profiles.getByName("EN16931")) { + if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung") || profile == Profiles.getByName("EN16931"))) { reason = "" + XMLTools.encodeXML(allowance.getReason()) + ""; } String reasonCode = ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 76abd748..d138bf15 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -86,6 +86,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { case "urn:factur-x.eu:1p0:minimum": return "MINIMUM"; case "urn:ferd:CrossIndustryDocument:invoice:1p0:extended": + case "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended": case "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended": return "EXTENDED"; default: diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index dc261749..eb063440 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -1,5 +1,6 @@ package org.mustangproject.ZUGFeRD; +import javax.xml.XMLConstants; import org.apache.commons.io.IOUtils; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; @@ -258,9 +259,14 @@ public class ZUGFeRDInvoiceImporter { } private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { - final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance(); - xmlFact.setNamespaceAware(true); - final DocumentBuilder builder = xmlFact.newDocumentBuilder(); + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setExpandEntityReferences(false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + 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); + final DocumentBuilder builder = dbf.newDocumentBuilder(); final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); /// is.skip(guessBOMSize(is)); document = builder.parse(is); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java index 8795b478..e133b028 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java @@ -21,6 +21,8 @@ package org.mustangproject.ZUGFeRD; import com.helger.commons.io.stream.StreamHelper; +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.IOUtils; import org.apache.fop.apps.*; import org.apache.fop.apps.io.ResourceResolverFactory; @@ -90,7 +92,8 @@ public class ZUGFeRDVisualizer { * @param fis inputstream (will be consumed) * @return (facturx = cii) */ - private EStandard findOutStandardFromRootNode(InputStream fis) { + private EStandard findOutStandardFromRootNode(InputStream fis) + throws ParserConfigurationException { String zf1Signature = "CrossIndustryDocument"; String zf2Signature = "CrossIndustryInvoice"; @@ -100,6 +103,11 @@ public class ZUGFeRDVisualizer { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); + dbf.setExpandEntityReferences(false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + 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); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new InputSource(fis)); @@ -121,12 +129,14 @@ public class ZUGFeRDVisualizer { return null; } - public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException { + public String visualize(String xmlFilename, Language lang) + throws IOException, TransformerException, ParserConfigurationException { FileInputStream fis = new FileInputStream(xmlFilename); return visualize(fis, lang); } - public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException { + public String visualize(InputStream inputXml, Language lang) + throws IOException, TransformerException, ParserConfigurationException { initTemplates(lang); String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8); @@ -211,7 +221,7 @@ public class ZUGFeRDVisualizer { } protected String toFOP(String xmlFilename) - throws IOException, TransformerException { + throws IOException, TransformerException, ParserConfigurationException { FileInputStream fis = new FileInputStream(xmlFilename); EStandard theStandard = findOutStandardFromRootNode(fis); @@ -264,7 +274,7 @@ public class ZUGFeRDVisualizer { */ try { fopInput = this.toFOP(XMLinputFile.getAbsolutePath()); - } catch (TransformerException | IOException e) { + } catch (TransformerException | IOException | ParserConfigurationException e) { LOGGER.error("Failed to apply FOP", e); } @@ -291,7 +301,7 @@ public class ZUGFeRDVisualizer { fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-( fopInput = toFOP(fis, theStandard); - } catch (TransformerException | IOException e) { + } catch (TransformerException | IOException | ParserConfigurationException e) { LOGGER.error("Failed to apply FOP", e); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java index 88c4fccf..2b63a5da 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java @@ -20,6 +20,7 @@ */ package org.mustangproject.ZUGFeRD; +import javax.xml.parsers.ParserConfigurationException; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language; @@ -76,9 +77,10 @@ public class VisualizationTest extends ResourceCase { fail("TransformerException should not happen: " + e.getMessage()); } catch (IOException e) { fail("IOException should not happen: " + e.getMessage()); + } catch (ParserConfigurationException e) { + fail("ParserConfigurationException should not happen: " + e.getMessage()); } - assertNotNull(result); /* remove file endings so that tests can also pass after checking out from git with arbitrary options (which may include CSRF changes) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index 5fc304ec..ace63df4 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -52,12 +52,17 @@ public class XRTest extends TestCase { TradeParty recipient = new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE"); recipient.setEmail("quack@ducktown.org"); Invoice i = createInvoice(recipient); - + String legalOrgID="aCustomSellerLegalOrgId"; + String sellerID="aSellerTradePartyID"; + i.getSender().setLegalOrganisation(new LegalOrganisation(legalOrgID)); + i.getSender().setID(sellerID); ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); zf2p.setProfile(Profiles.getByName("XRechnung")); zf2p.generateXML(i); String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8); assertTrue(theXML.contains(""+sellerID+""));// must be possible without scheme # + assertTrue(theXML.contains(""+legalOrgID+""));// must be possible without scheme # assertThat(theXML).valueByXPath("count(//*[local-name()='IncludedSupplyChainTradeLineItem'])") .asInt() .isEqualTo(1); //2 errors are OK because there is a known bug diff --git a/pom.xml b/pom.xml index 45aa6c60..5cc3f0ac 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.16.3-SNAPSHOT pom + 2.16.4-SNAPSHOT pom Mustang diff --git a/validator/pom.xml b/validator/pom.xml index ab6398d7..8d8b7ff0 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT @@ -38,7 +38,7 @@ ${project.groupId} library - 2.16.3-SNAPSHOT + 2.16.4-SNAPSHOT org.dom4j diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index c8bd3475..58dba911 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -10,6 +10,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Calendar; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.stream.StreamSource; @@ -151,6 +152,11 @@ public class XMLValidator extends Validator { final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); // otherwise we can not act namespace independently, i.e. use // document.getElementsByTagNameNS("*",... + dbf.setExpandEntityReferences(false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + 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); final DocumentBuilder db = dbf.newDocumentBuilder(); final InputSource is = new InputSource(new StringReader(zfXML)); diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index d21317b1..cdab1874 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -17,6 +17,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -142,6 +143,12 @@ public class ZUGFeRDValidator { String xmlAsString = null; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setExpandEntityReferences(false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + 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); DocumentBuilder db = dbf.newDocumentBuilder(); content = XMLTools.removeBOM(content);