diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000..5b388a8 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,28 @@ +# This is a basic workflow to help you get started with Actions + +name: Codecov + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + pull_request: + + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Run cobertura + run: mvn cobertura:cobertura + + - name: Upload report + run: bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..87ce66d --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,31 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Build + +on: + push: + pull_request: + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [7, 8, 9.0.x, 10, 11, 12, 13] + + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Clean Build + run: mvn clean + + - name: Build with Maven + run: mvn -B package --file pom.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index 762f6e9..84a1064 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ /.classpath /.project /.settings +/.envrc /mvn -/HOWTO_RELEASE.txt /bin /target /java-xmlbuilder diff --git a/CHANGES.md b/CHANGES.md index 5154782..4b2435f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,17 +1,37 @@ Release Notes for java-xmlbuilder ================================= -Version 1.2 - Pending ---------------------- +Version 1.3 - 8 July 2020 +------------------------- + +Fixes: + +* Update source version from 1.5 to 1.7 to work with Java versions 11+ which no + longer supports `javax.annotation` and because 6 no longer builds with Maven. + +* Tweaks to pom.xml to get Maven builds to work with Java 12. + +* Replace references to my defunct website with working alternatives. + +Version 1.2 - 1 September 2017 +------------------------------ Fixes: * Prevent XML External Entity (XXE) injection attacks by disabling parsing of general and parameter external entities by default (#6). External entities are now only parsed if this feature is explicitly enabled by passing a boolean - flag value to the #create and #parse methods. + flag value to the #create and #parse methods. WARNING: This will break code that expects external entities to be parsed. +Enhancements: + +* Permit users to disable namespace-awareness in the underlying + DocumentBuilderFactory when constructing the builder with extended `create()` + and `parse()` methods. Namespace awareness is enabled by default unless you + use the more explicit versions of these methods that take additional + `enableExternalEntities` and `isNamespaceAware` parameters. + Version 1.1 - 22 July 2014 -------------------------- @@ -19,12 +39,12 @@ Added a new `XMLBuilder2` implementation that avoids checked exceptions in the API and throws runtime exceptions instead. This should make the library much more pleasant to use, and your code much cleaner, in situations where low-level exceptions are unlikely -- which is probably most situations where you would -use this library. +use this library. For example when creating a new document with the `#create` method, instead of needing to explicitly catch the unlikely `ParserConfigurationException`, if you use `XMLBuilder2` this exception automatically gets wrapped in the new -`XMLBuilderRuntimeException` class and can be left to propagate out. +`XMLBuilderRuntimeException` class and can be left to propagate out. Aside from the removal of checked exceptions, `XMLBuilder2` has the same API as the original `XMLBuilder` and should therefore be a drop-in replacement in @@ -37,9 +57,9 @@ Version 1.0 - 6 March 2014 -------------------------- Jumped version number from 0.7 to 1.0 to better reflect this project's age -and stability, as well as to celebrate the move to GitHub. +and stability, as well as to celebrate the move to GitHub. -* Migrated project from +* Migrated project from [Google Code](https://code.google.com/p/java-xmlbuilder/) to [GitHub](https://github.com/jmurty/java-xmlbuilder). Whew, that's better! * Test cases for edge-case issues and questions reported by users. @@ -83,7 +103,7 @@ Version 0.3 - 2 July 2010 * First release to Maven repository. * Parse existing XML documents with `parse` method. -* Find specific nodes in document with an XPath with `xpathFind`. +* Find specific nodes in document with an XPath with `xpathFind`. * Added JUnit tests Version 0.2 - 6 January 2009 diff --git a/README.md b/README.md index 0740c1c..51bda75 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ java-xmlbuilder =============== +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jamesmurty.utils/java-xmlbuilder/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jamesmurty.utils/java-xmlbuilder) + +![Build](https://github.com/jmurty/java-xmlbuilder/workflows/Build/badge.svg) ![Codecov](https://github.com/jmurty/java-xmlbuilder/workflows/Codecov/badge.svg) + +[![codecov](https://codecov.io/gh/jmurty/java-xmlbuilder/branch/master/graph/badge.svg)](https://codecov.io/gh/jmurty/java-xmlbuilder) + XML Builder is a utility that allows simple XML documents to be constructed using relatively sparse Java code. @@ -24,14 +30,14 @@ Since version 1.1 this library provides two builder implementations and APIs: * `XMLBuilder` – the original API – follows standard Java practice of re-throwing lower level checked exceptions when you do things like create a - new document. + new document. You must explicitly `catch` these checked exceptions in your codebase, even - though they are unlikely to occur in tested code. + though they are unlikely to occur in tested code. * `XMLBuilder2` is a newer API that removes checked exceptions altogether, and will instead wrap and propagate lower level exceptions in an unchecked - `XMLBuilderRuntimeException`. + `XMLBuilderRuntimeException`. Use this class if you don't like the code mess or overhead of try/catching - many low-level exceptions that are unlikely to occur in practice. + many low-level exceptions that are unlikely to occur in practice. Both these versions work identically apart from the handling of errors, so you can use whichever version you prefer or "upgrade" from one to the other in @@ -73,11 +79,11 @@ Produces this XML document: Getting Started --------------- -See further example usage below and in the -[JavaDoc documentation](http://s3.jamesmurty.com/java-xmlbuilder/index.html). +See further example usage below and in the +[JavaDoc documentation](http://s3.james.murty.co/java-xmlbuilder/index.html). Download a Jar file containing the latest version -[java-xmlbuilder-1.1.jar](http://s3.jamesmurty.com/java-xmlbuilder/java-xmlbuilder-1.1.jar). +[java-xmlbuilder-1.3.jar](http://s3.james.murty.co/java-xmlbuilder/java-xmlbuilder-1.3.jar). Maven users can add this project as a dependency with the following additions to a POM.xml file: @@ -88,7 +94,7 @@ to a POM.xml file: com.jamesmurty.utils java-xmlbuilder - 1.1 + 1.3 . . . @@ -193,7 +199,7 @@ methods. XMLBuilder builder = XMLBuilder.create("Projects") .e("java-xmlbuilder") .a("language", "Java") - .a("scm","SVN") + .a("scm","SVN") .e("Location") .a("type", "URL") .t("http://code.google.com/p/java-xmlbuilder/") @@ -221,7 +227,7 @@ The following methods are available for adding items to the XML document: ### Output -XMLBuilder includes two convenient methods for outputting a document. +XMLBuilder includes two convenient methods for outputting a document. You can use the `toWriter` method to print the document to an output stream or file: @@ -238,7 +244,7 @@ builder.asString(outputProperties); Both of these output methods take an `outputProperties` parameter that you can use to control how the output is generated. Any output properties you provide are forwarded to the underlying Transformer object that is used to serialize -the XML document. +the XML document. You might specify any non-standard properties like so: @@ -284,7 +290,7 @@ represents the document's root element, no matter deep an element hierarchy your code has built: ```java -org.w3c.dom.Element rootElement = +org.w3c.dom.Element rootElement = XMLBuilder.create("This") .e("Element") .e("Hierarchy") @@ -351,6 +357,22 @@ To produce: ``` +### Configuring advanced features + +When creating or parsing a document you can enable and disable advanced +features by using the more explicit versions of the `parse()` and `create()` +constructors. + +You can: + +* use the `enableExternalEntities` flag to enable or disable external entities. + NOTE: you should leave these disabled, as they are by default, unless you + really need them because they open you to XML External Entity (XXE) injection + attacks. +* use the `isNamespaceAware` flag to enable or disable namespace awareness in + the underlying `DocumentBuilderFactory`. + + Release History --------------- diff --git a/pom.xml b/pom.xml index 5a84930..cecbdb2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,15 @@ java-xmlbuilder jar - 1.2-SNAPSHOT + 1.4-SNAPSHOT java-xmlbuilder XML Builder is a utility that creates simple XML documents using relatively sparse Java code https://github.com/jmurty/java-xmlbuilder + + + 1.7 + 1.7 + @@ -28,7 +33,7 @@ jmurty James Murty - http://jamesmurty.com + https://github.com/jmurty developer @@ -46,7 +51,7 @@ junit junit - 4.11 + 4.13.1 test @@ -89,6 +94,9 @@ org.apache.maven.plugins maven-javadoc-plugin 2.9 + + 7 + attach-javadocs @@ -98,7 +106,19 @@ - + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + + html + xml + + + + diff --git a/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java b/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java index 90e26d5..3122a30 100644 --- a/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java +++ b/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 James Murty (www.jamesmurty.com) + * Copyright 2008-2020 James Murty (github.com/jmurty) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,8 +74,6 @@ public abstract class BaseXMLBuilder { */ private Node xmlNode = null; - private static boolean isNamespaceAware = true; - /** * If true, the builder will raise an {@link XMLBuilderRuntimeException} * if external general and parameter entities cannot be explicitly enabled @@ -199,14 +197,20 @@ protected static void enableOrDisableExternalEntityParsing( * the name of the document's root element. * @param namespaceURI * default namespace URI for document, ignored if null or empty. + * @param enableExternalEntities + * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * an XML Document. * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz */ protected static Document createDocumentImpl( - String name, String namespaceURI, boolean enableExternalEntities) + String name, String namespaceURI, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, FactoryConfigurationError { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -229,17 +233,23 @@ protected static Document createDocumentImpl( * * @param inputSource * an XML document input source that will be parsed into a DOM. + * @param enableExternalEntities + * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. - * @throws ParserConfigurationException * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ protected static Document parseDocumentImpl( - InputSource inputSource, boolean enableExternalEntities) + InputSource inputSource, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -258,7 +268,7 @@ protected static Document parseDocumentImpl( * Uses approach I documented on StackOverflow: * http://stackoverflow.com/a/979606/4970 * - * @throws XPathExpressionException + * @throws XPathExpressionException xyz */ protected void stripWhitespaceOnlyTextNodesImpl() throws XPathExpressionException @@ -287,7 +297,7 @@ protected void stripWhitespaceOnlyTextNodesImpl() * * @return * a builder node at the same location as before the operation. - * @throws XPathExpressionException + * @throws XPathExpressionException xyz */ public abstract BaseXMLBuilder stripWhitespaceOnlyTextNodes() throws XPathExpressionException; @@ -523,7 +533,7 @@ protected String lookupNamespaceURIImpl(String name) { * the name of the XML element. * @param namespaceURI * a namespace URI - * @return + * @return xyz * * @throws IllegalStateException * if you attempt to add a child element to an XML node that already @@ -747,6 +757,7 @@ protected Element elementImpl(String name, String namespaceURI) { * * @param name * the name of the XML element. + * @return xyz * * @throws IllegalStateException * if you attempt to add a sibling element to a node where there are already @@ -766,6 +777,7 @@ protected Element elementBeforeImpl(String name) { * the name of the XML element. * @param namespaceURI * a namespace URI + * @return xyz * * @throws IllegalStateException * if you attempt to add a sibling element to a node where there are already @@ -1243,6 +1255,8 @@ protected Node upImpl(int steps) { } /** + * @param anXmlElement xyz + * * @throws IllegalStateException * if the current element contains any child text nodes that aren't pure whitespace. * We allow whitespace so parsed XML documents containing indenting or pretty-printing @@ -1288,7 +1302,7 @@ protected void assertElementContainsNoOrWhitespaceOnlyTextNodes(Node anXmlElemen * null or an empty Properties object, in which case the default output * properties will be applied. * - * @throws TransformerException + * @throws TransformerException xyz */ public void toWriter(boolean wholeDocument, Writer writer, Properties outputProperties) throws TransformerException { @@ -1327,7 +1341,7 @@ public void toWriter(boolean wholeDocument, Writer writer, Properties outputProp * null or an empty Properties object, in which case the default output * properties will be applied. * - * @throws TransformerException + * @throws TransformerException xyz */ public void toWriter(Writer writer, Properties outputProperties) throws TransformerException { @@ -1348,7 +1362,7 @@ public void toWriter(Writer writer, Properties outputProperties) * @return * the XML document as a string * - * @throws TransformerException + * @throws TransformerException xyz */ public String asString(Properties outputProperties) throws TransformerException { StringWriter writer = new StringWriter(); @@ -1370,7 +1384,7 @@ public String asString(Properties outputProperties) throws TransformerException * @return * the XML document as a string * - * @throws TransformerException + * @throws TransformerException xyz */ public String elementAsString(Properties outputProperties) throws TransformerException { StringWriter writer = new StringWriter(); @@ -1385,7 +1399,7 @@ public String elementAsString(Properties outputProperties) throws TransformerExc * the XML document as a string without the XML declaration at the * beginning of the output. * - * @throws TransformerException + * @throws TransformerException xyz */ public String asString() throws TransformerException { Properties outputProperties = new Properties(); @@ -1401,7 +1415,7 @@ public String asString() throws TransformerException { * the XML document as a string without the XML declaration at the * beginning of the output. * - * @throws TransformerException + * @throws TransformerException xyz */ public String elementAsString() throws TransformerException { Properties outputProperties = new Properties(); @@ -1428,4 +1442,4 @@ protected String getPrefixFromQualifiedName(String qualifiedName) { } } -} \ No newline at end of file +} diff --git a/src/main/java/com/jamesmurty/utils/XMLBuilder.java b/src/main/java/com/jamesmurty/utils/XMLBuilder.java index 5663b44..144293f 100644 --- a/src/main/java/com/jamesmurty/utils/XMLBuilder.java +++ b/src/main/java/com/jamesmurty/utils/XMLBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 James Murty (www.jamesmurty.com) + * Copyright 2008-2020 James Murty (github.com/jmurty) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,18 +101,22 @@ protected XMLBuilder(Node myNode, Node parentNode) { * default namespace URI for document, ignored if null or empty. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz */ public static XMLBuilder create(String name, String namespaceURI, - boolean enableExternalEntities) + boolean enableExternalEntities, boolean isNamespaceAware) throws ParserConfigurationException, FactoryConfigurationError { return new XMLBuilder( - createDocumentImpl(name, namespaceURI, enableExternalEntities)); + createDocumentImpl( + name, namespaceURI, enableExternalEntities, isNamespaceAware)); } /** @@ -124,16 +128,20 @@ public static XMLBuilder create(String name, String namespaceURI, * the name of the document's root element. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz */ - public static XMLBuilder create(String name, boolean enableExternalEntities) + public static XMLBuilder create(String name, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, FactoryConfigurationError { - return create(name, null, enableExternalEntities); + return create(name, null, enableExternalEntities, isNamespaceAware); } /** @@ -146,16 +154,17 @@ public static XMLBuilder create(String name, boolean enableExternalEntities) * the name of the document's root element. * @param namespaceURI * default namespace URI for document, ignored if null or empty. + * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz */ public static XMLBuilder create(String name, String namespaceURI) throws ParserConfigurationException, FactoryConfigurationError { - return create(name, namespaceURI, false); + return create(name, namespaceURI, false, true); } /** @@ -168,8 +177,8 @@ public static XMLBuilder create(String name, String namespaceURI) * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz */ public static XMLBuilder create(String name) throws ParserConfigurationException, FactoryConfigurationError @@ -186,21 +195,26 @@ public static XMLBuilder create(String name) * an XML document input source that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. - * @throws ParserConfigurationException * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ public static XMLBuilder parse( - InputSource inputSource, boolean enableExternalEntities) + InputSource inputSource, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, SAXException, IOException { return new XMLBuilder( - parseDocumentImpl(inputSource, enableExternalEntities)); + parseDocumentImpl( + inputSource, enableExternalEntities, isNamespaceAware)); } /** @@ -212,22 +226,27 @@ public static XMLBuilder parse( * an XML document string that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws ParserConfigurationException - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ public static XMLBuilder parse( - String xmlString, boolean enableExternalEntities) + String xmlString, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, SAXException, IOException { return XMLBuilder.parse( new InputSource(new StringReader(xmlString)), - enableExternalEntities); + enableExternalEntities, + isNamespaceAware); } /** @@ -239,20 +258,26 @@ public static XMLBuilder parse( * an XML document file that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws ParserConfigurationException - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ - public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities) + public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities, + boolean isNamespaceAware) throws ParserConfigurationException, SAXException, IOException { return XMLBuilder.parse( - new InputSource(new FileReader(xmlFile)), enableExternalEntities); + new InputSource(new FileReader(xmlFile)), + enableExternalEntities, + isNamespaceAware); } /** @@ -264,17 +289,17 @@ public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities) * an XML document input source that will be parsed into a DOM. * @return * a builder node that can be used to add more nodes to the XML document. - * @throws ParserConfigurationException * - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ public static XMLBuilder parse(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException { - return XMLBuilder.parse(inputSource, false); + return XMLBuilder.parse(inputSource, false, true); } /** @@ -287,16 +312,16 @@ public static XMLBuilder parse(InputSource inputSource) * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws ParserConfigurationException - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ public static XMLBuilder parse(String xmlString) throws ParserConfigurationException, SAXException, IOException { - return XMLBuilder.parse(xmlString, false); + return XMLBuilder.parse(xmlString, false, true); } /** @@ -309,16 +334,16 @@ public static XMLBuilder parse(String xmlString) * @return * a builder node that can be used to add more nodes to the XML document. * - * @throws ParserConfigurationException - * @throws FactoryConfigurationError - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException xyz + * @throws FactoryConfigurationError xyz + * @throws ParserConfigurationException xyz + * @throws IOException xyz + * @throws SAXException xyz */ public static XMLBuilder parse(File xmlFile) throws ParserConfigurationException, SAXException, IOException { - return XMLBuilder.parse(xmlFile, false); + return XMLBuilder.parse(xmlFile, false, true); } @Override diff --git a/src/main/java/com/jamesmurty/utils/XMLBuilder2.java b/src/main/java/com/jamesmurty/utils/XMLBuilder2.java index 013ad7c..6f0cda6 100644 --- a/src/main/java/com/jamesmurty/utils/XMLBuilder2.java +++ b/src/main/java/com/jamesmurty/utils/XMLBuilder2.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 James Murty (www.jamesmurty.com) + * Copyright 2008-2020 James Murty (github.com/jmurty) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,17 +120,22 @@ private static RuntimeException wrapExceptionAsRuntimeException(Exception e) { * default namespace URI for document, ignored if null or empty. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * @throws XMLBuilderRuntimeException * to wrap {@link ParserConfigurationException} */ public static XMLBuilder2 create( - String name, String namespaceURI, boolean enableExternalEntities) + String name, String namespaceURI, boolean enableExternalEntities, + boolean isNamespaceAware) { try { return new XMLBuilder2( - createDocumentImpl(name, namespaceURI, enableExternalEntities)); + createDocumentImpl( + name, namespaceURI, enableExternalEntities, isNamespaceAware)); } catch (ParserConfigurationException e) { throw wrapExceptionAsRuntimeException(e); } @@ -145,14 +150,19 @@ public static XMLBuilder2 create( * the name of the document's root element. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * @throws XMLBuilderRuntimeException * to wrap {@link ParserConfigurationException} */ - public static XMLBuilder2 create(String name, boolean enableExternalEntities) + public static XMLBuilder2 create(String name, + boolean enableExternalEntities, boolean isNamespaceAware) { - return XMLBuilder2.create(name, null, enableExternalEntities); + return XMLBuilder2.create( + name, null, enableExternalEntities, isNamespaceAware); } /** @@ -172,7 +182,7 @@ public static XMLBuilder2 create(String name, boolean enableExternalEntities) */ public static XMLBuilder2 create(String name, String namespaceURI) { - return XMLBuilder2.create(name, namespaceURI, false); + return XMLBuilder2.create(name, namespaceURI, false, true); } /** @@ -189,7 +199,7 @@ public static XMLBuilder2 create(String name, String namespaceURI) */ public static XMLBuilder2 create(String name) { - return XMLBuilder2.create(name, null, false); + return XMLBuilder2.create(name, null, false, true); } /** @@ -201,6 +211,9 @@ public static XMLBuilder2 create(String name) * an XML document input source that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * @throws XMLBuilderRuntimeException @@ -208,11 +221,13 @@ public static XMLBuilder2 create(String name) * {@link IOException} */ public static XMLBuilder2 parse( - InputSource inputSource, boolean enableExternalEntities) + InputSource inputSource, boolean enableExternalEntities, + boolean isNamespaceAware) { try { return new XMLBuilder2( - parseDocumentImpl(inputSource, enableExternalEntities)); + parseDocumentImpl( + inputSource, enableExternalEntities, isNamespaceAware)); } catch (ParserConfigurationException e) { throw wrapExceptionAsRuntimeException(e); } catch (SAXException e) { @@ -231,15 +246,19 @@ public static XMLBuilder2 parse( * an XML document string that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. */ public static XMLBuilder2 parse( - String xmlString, boolean enableExternalEntities) + String xmlString, boolean enableExternalEntities, boolean isNamespaceAware) { return XMLBuilder2.parse( new InputSource(new StringReader(xmlString)), - enableExternalEntities); + enableExternalEntities, + isNamespaceAware); } /** @@ -251,18 +270,23 @@ public static XMLBuilder2 parse( * an XML document file that will be parsed into a DOM. * @param enableExternalEntities * enable external entities; beware of XML External Entity (XXE) injection. + * @param isNamespaceAware + * enable or disable namespace awareness in the underlying + * {@link DocumentBuilderFactory} * @return * a builder node that can be used to add more nodes to the XML document. * @throws XMLBuilderRuntimeException * to wrap {@link ParserConfigurationException}, {@link SAXException}, * {@link IOException}, {@link FileNotFoundException} */ - public static XMLBuilder2 parse(File xmlFile, boolean enableExternalEntities) + public static XMLBuilder2 parse(File xmlFile, boolean enableExternalEntities, + boolean isNamespaceAware) { try { return XMLBuilder2.parse( new InputSource(new FileReader(xmlFile)), - enableExternalEntities); + enableExternalEntities, + isNamespaceAware); } catch (FileNotFoundException e) { throw wrapExceptionAsRuntimeException(e); } @@ -283,7 +307,7 @@ public static XMLBuilder2 parse(File xmlFile, boolean enableExternalEntities) */ public static XMLBuilder2 parse(InputSource inputSource) { - return XMLBuilder2.parse(inputSource, false); + return XMLBuilder2.parse(inputSource, false, true); } /** @@ -298,7 +322,7 @@ public static XMLBuilder2 parse(InputSource inputSource) */ public static XMLBuilder2 parse(String xmlString) { - return XMLBuilder2.parse(xmlString, false); + return XMLBuilder2.parse(xmlString, false, true); } /** @@ -316,7 +340,7 @@ public static XMLBuilder2 parse(String xmlString) */ public static XMLBuilder2 parse(File xmlFile) { - return XMLBuilder2.parse(xmlFile, false); + return XMLBuilder2.parse(xmlFile, false, true); } /** @@ -526,7 +550,7 @@ public XMLBuilder2 namespace(String prefix, String namespaceURI) { @Override public XMLBuilder2 ns(String prefix, String namespaceURI) { - return attribute(prefix, namespaceURI); + return namespace(prefix, namespaceURI); } @Override diff --git a/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java b/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java index 54959fc..274d461 100644 --- a/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java +++ b/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java @@ -6,7 +6,6 @@ import java.io.StringWriter; import java.util.Properties; -import javax.annotation.Resource; import javax.xml.transform.OutputKeys; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; @@ -54,11 +53,12 @@ protected BaseXMLBuilder XMLBuilder_parse(InputSource source) throws Exception { } protected BaseXMLBuilder XMLBuilder_parse( - String documentString, boolean enableExternalEntities) throws Exception + String documentString, boolean enableExternalEntities, + boolean isNamespaceAware) throws Exception { return (BaseXMLBuilder) XMLBuilderToTest().getMethod( - "parse", String.class, boolean.class).invoke( - null, documentString, enableExternalEntities); + "parse", String.class, boolean.class, boolean.class).invoke( + null, documentString, enableExternalEntities, isNamespaceAware); } protected BaseXMLBuilder XMLBuilder_parse(String documentString) throws Exception { @@ -469,6 +469,44 @@ public void testNamespaces() throws Exception { builder.element("undefined-prefix:ElementName"); } + public void testNamespaceUnawareBuilder() throws Exception { + String XML_WITH_NAMESPACES = + "" + + "Found me" + + ""; + + // Builder set to be unaware of namespaces can traverse DOM with + // namespaces without using namespace prefixes + BaseXMLBuilder result = XMLBuilder_parse( + XML_WITH_NAMESPACES, + false, // enableExternalEntities + false // isNamespaceAware + ).xpathFind("/NamespaceUnwareTest/NestedElement"); + assertEquals("Found me", result.getElement().getTextContent()); + + // Builder set to be aware of namespaces (per default) cannot traverse + // DOM with namespaces without using namespace prefixes + try { + result = XMLBuilder_parse(XML_WITH_NAMESPACES) + .xpathFind("/NamespaceUnwareTest/NestedElement"); + } catch (Exception ex) { + Throwable cause = null; + if (this instanceof TestXMLBuilder2) { + cause = ex.getCause(); // Exception wrapped in runtime ex + } else { + cause = ex; + } + assertEquals( + cause.getClass(), XPathExpressionException.class); + assertTrue( + cause.getMessage().contains( + "XPath expression \"/NamespaceUnwareTest/NestedElement\"" + + " does not resolve to an Element in context" + )); + } + assertEquals("Found me", result.getElement().getTextContent()); + } + public void testElementBefore() throws Exception { BaseXMLBuilder builder = XMLBuilder_create("TestDocument", "urn:default") .namespace("custom", "urn:custom") @@ -648,7 +686,7 @@ public void testXMLBuilderParserImmuneToXXEAttackByDefault() throws Exception { String parsedXml = builder.asString(); assertFalse(parsedXml.indexOf("Injected XXE Data") >= 0); // If you enable external entity processing, builder becomes subject to XXE injection - builder = XMLBuilder_parse(XML_DOC_WITH_XXE, true); + builder = XMLBuilder_parse(XML_DOC_WITH_XXE, true, true); parsedXml = builder.asString(); assertTrue(parsedXml.indexOf("Injected XXE Data") >= 0); }