diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java index 545cdd79687..e5639079325 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java @@ -16,13 +16,9 @@ */ package org.apache.activemq.artemis.cli.commands.tools.xml; -import javax.xml.XMLConstants; -import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.stax.StAXSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.File; import java.io.FileInputStream; @@ -63,6 +59,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.utils.ClassloadingUtil; import org.apache.activemq.artemis.utils.ListUtil; +import org.apache.activemq.artemis.utils.XmlProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; @@ -175,7 +172,7 @@ public void process(InputStream inputStream, ClientSession session) throws Excep public void process(InputStream inputStream, ClientSession session, ClientSession managementSession) throws Exception { - reader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream); + reader = XmlProvider.createXMLStreamReader(inputStream); messageReader = new XMLMessageImporter(reader, session); messageReader.setOldPrefixTranslation(oldPrefixTranslation); @@ -219,11 +216,8 @@ public void validate(String fileName) throws Exception { } public void validate(InputStream inputStream) throws Exception { - XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream); - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - Schema schema = factory.newSchema(XmlDataImporter.findResource("schema/artemis-import-export.xsd")); - - Validator validator = schema.newValidator(); + XMLStreamReader reader = XmlProvider.createXMLStreamReader(inputStream); + Validator validator = XmlProvider.newValidator(XmlDataImporter.findResource("schema/artemis-import-export.xsd")); validator.validate(new StAXSource(reader)); reader.close(); } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/serialize/XMLMessageSerializer.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/serialize/XMLMessageSerializer.java index e353b94240d..06623787c04 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/serialize/XMLMessageSerializer.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/serialize/XMLMessageSerializer.java @@ -18,7 +18,6 @@ import javax.jms.Message; import javax.jms.Session; -import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; @@ -36,6 +35,7 @@ import org.apache.activemq.artemis.cli.commands.tools.xml.XmlDataExporter; import org.apache.activemq.artemis.jms.client.ActiveMQMessage; import org.apache.activemq.artemis.jms.client.ActiveMQSession; +import org.apache.activemq.artemis.utils.XmlProvider; public class XMLMessageSerializer implements MessageSerializer { @@ -87,7 +87,7 @@ public void setOutput(OutputStream outputStream) throws Exception { @Override public void setInput(InputStream inputStream, Session session) throws Exception { - XMLStreamReader streamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream); + XMLStreamReader streamReader = XmlProvider.createXMLStreamReader(inputStream); this.clientSession = ((ActiveMQSession) session).getCoreSession(); this.reader = new XMLMessageImporter(streamReader, clientSession); } diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java index 3d09920b72e..fab3eb1110f 100644 --- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java +++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java @@ -29,10 +29,8 @@ import org.apache.activemq.artemis.json.JsonArray; import org.apache.activemq.artemis.json.JsonObject; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.BufferedReader; @@ -94,6 +92,7 @@ import org.apache.activemq.artemis.utils.SensitiveDataCodec; import org.apache.activemq.artemis.utils.StringUtil; import org.apache.activemq.artemis.utils.Wait; +import org.apache.activemq.artemis.utils.XmlProvider; import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; import org.apache.commons.configuration2.builder.fluent.Configurations; @@ -368,8 +367,7 @@ public void testSecurityManagerConfiguration() throws Exception { originalBootstrapFile.delete(); // write the modified config into the bootstrap.xml file - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - Transformer transformer = transformerFactory.newTransformer(); + Transformer transformer = XmlProvider.newTransformer(); DOMSource source = new DOMSource(config); StreamResult streamResult = new StreamResult(originalBootstrapFile); transformer.transform(source, streamResult); @@ -2195,8 +2193,7 @@ public boolean isWindows() { } private static Document parseXml(File xmlFile) throws ParserConfigurationException, IOException, SAXException { - DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder domBuilder = domFactory.newDocumentBuilder(); + DocumentBuilder domBuilder = XmlProvider.newDocumentBuilder(); return domBuilder.parse(xmlFile); } diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/XmlProvider.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/XmlProvider.java new file mode 100644 index 00000000000..07ecc95d4e7 --- /dev/null +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/XmlProvider.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.utils; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.util.Map; + +import org.w3c.dom.ls.LSInput; +import org.xml.sax.SAXException; + +public class XmlProvider { + public static final String ARTEMIS_DISABLE_XXE_PROPERTY = "artemis.disableXxe"; + + public static final String XINCLUDE_AWARE_PROPERTY = "XINCLUDE_AWARE"; + public static final String NAMESPACE_AWARE_PROPERTY = "NAMESPACE_AWARE"; + public static final String IGNORE_COMMENTS_PROPERTY = "IGNORE_COMMENTS"; + public static final String IGNORE_ELEMENT_CONTENT_WHITESPACE_PROPERTY = "IGNORE_ELEMENT_CONTENT_WHITESPACE"; + + private static final String ACTIVEMQ_CORE_NS = "urn:activemq:core"; + private static final String ACTIVEMQ_JMS_NS = "urn:activemq:jms"; + + private static final String ARTEMIS_XML_SCHEMA_SID = "xml.xsd"; + private static final String ARTEMIS_CONFIGURATION_SCHEMA_SID = "artemis-configuration.xsd"; + private static final String ARTEMIS_JMS_SCHEMA_SID = "artemis-jms.xsd"; + + private static final String ARTEMIS_SCHEMA_BASE_URL = "schema/"; + private static final String ARTEMIS_XML_SCHEMA_URL = ARTEMIS_SCHEMA_BASE_URL + ARTEMIS_XML_SCHEMA_SID; + private static final String ARTEMIS_CONFIGURATION_SCHEMA_URL = ARTEMIS_SCHEMA_BASE_URL + ARTEMIS_CONFIGURATION_SCHEMA_SID; + private static final String ARTEMIS_JMS_SCHEMA_URL = ARTEMIS_SCHEMA_BASE_URL + ARTEMIS_JMS_SCHEMA_SID; + + private static boolean xxeEnabled = !"".equals(System.getProperty(ARTEMIS_DISABLE_XXE_PROPERTY)) && + !Boolean.parseBoolean(System.getProperty(ARTEMIS_DISABLE_XXE_PROPERTY, Boolean.FALSE.toString())); + + public static boolean isXxeEnabled() { + return xxeEnabled; + } + + public static void setXxeEnabled(boolean enabled) { + xxeEnabled = enabled; + } + + public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { + return newDocumentBuilder(null, null); + } + + public static DocumentBuilder newDocumentBuilder(Map features, Map properties) throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + if (features != null) { + for (Map.Entry feature : features.entrySet()) { + factory.setFeature(feature.getKey(), feature.getValue()); + } + } + + if (properties != null) { + for (Map.Entry property : properties.entrySet()) { + if (XINCLUDE_AWARE_PROPERTY.equals(property.getKey())) { + factory.setXIncludeAware(property.getValue()); + } else if (NAMESPACE_AWARE_PROPERTY.equals(property.getKey())) { + factory.setNamespaceAware(property.getValue()); + } else if (IGNORE_COMMENTS_PROPERTY.equals(property.getKey())) { + factory.setIgnoringComments(property.getValue()); + } else if (IGNORE_ELEMENT_CONTENT_WHITESPACE_PROPERTY.equals(property.getKey())) { + factory.setIgnoringElementContentWhitespace(property.getValue()); + } else { + throw new IllegalArgumentException("Property not supported: " + property.getKey()); + } + } + } + + if (!isXxeEnabled()) { + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + } + + return factory.newDocumentBuilder(); + } + + public static XMLStreamReader createXMLStreamReader(InputStream inputStream) throws XMLStreamException { + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + + if (!isXxeEnabled()) { + // This disables DTDs entirely for that factory + xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + // disable external entities + xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); + } + + return xmlInputFactory.createXMLStreamReader(inputStream); + } + + public static Schema newSchema(Source schema, Map features) throws SAXException { + return newSchemaFactory(features).newSchema(schema); + } + + private static SchemaFactory newSchemaFactory(Map features) throws SAXException { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + if (features != null) { + for (Map.Entry feature : features.entrySet()) { + factory.setFeature(feature.getKey(), feature.getValue()); + } + } + + if (!isXxeEnabled()) { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> { + if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type) && XMLConstants.XML_NS_URI.equals(namespaceURI) && ARTEMIS_XML_SCHEMA_SID.equals(systemId)) { + return newLSInput(publicId, systemId, baseURI, Thread.currentThread().getContextClassLoader().getResourceAsStream(ARTEMIS_XML_SCHEMA_URL)); + } else if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type) && ACTIVEMQ_CORE_NS.equals(namespaceURI) && ARTEMIS_CONFIGURATION_SCHEMA_SID.equals(systemId)) { + return newLSInput(publicId, systemId, baseURI, Thread.currentThread().getContextClassLoader().getResourceAsStream(ARTEMIS_CONFIGURATION_SCHEMA_URL)); + } else if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type) && ACTIVEMQ_JMS_NS.equals(namespaceURI) && ARTEMIS_JMS_SCHEMA_SID.equals(systemId)) { + return newLSInput(publicId, systemId, baseURI, Thread.currentThread().getContextClassLoader().getResourceAsStream(ARTEMIS_JMS_SCHEMA_URL)); + } + + return null; + }); + } + + return factory; + } + + public static Transformer newTransformer() throws TransformerConfigurationException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + if (!isXxeEnabled()) { + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + } + + return transformerFactory.newTransformer(); + } + + public static Validator newValidator(URL schema) throws SAXException { + Validator validator = newSchemaFactory(null).newSchema(schema).newValidator(); + + if (!isXxeEnabled()) { + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } + + return validator; + } + + private static LSInput newLSInput(String publicId, String systemId, String baseURI, InputStream byteStream) { + return new LSInput() { + @Override + public Reader getCharacterStream() { + return null; + } + + @Override + public void setCharacterStream(Reader reader) { + + } + + @Override + public InputStream getByteStream() { + return byteStream; + } + + @Override + public void setByteStream(InputStream inputStream) { + + } + + @Override + public String getStringData() { + return null; + } + + @Override + public void setStringData(String s) { + + } + + @Override + public String getSystemId() { + return systemId; + } + + @Override + public void setSystemId(String s) { + + } + + @Override + public String getPublicId() { + return publicId; + } + + @Override + public void setPublicId(String s) { + + } + + @Override + public String getBaseURI() { + return baseURI; + } + + @Override + public void setBaseURI(String s) { + + } + + @Override + public String getEncoding() { + return null; + } + + @Override + public void setEncoding(String s) { + + } + + @Override + public boolean getCertifiedText() { + return false; + } + + @Override + public void setCertifiedText(boolean b) { + + } + }; + } +} diff --git a/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/XmlProviderTest.java b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/XmlProviderTest.java new file mode 100644 index 00000000000..989ae50f6e2 --- /dev/null +++ b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/XmlProviderTest.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.utils; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Validator; +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +@RunWith(Parameterized.class) +public class XmlProviderTest { + + @Parameterized.Parameters(name = "xxeEnabled={0}") + public static Collection getParameters() { + return Arrays.asList(new Boolean[]{true, false}); + } + + public XmlProviderTest(boolean xxeEnabled) { + XmlProvider.setXxeEnabled(xxeEnabled); + } + + @Test + public void testDocument() throws Exception { + DocumentBuilder documentBuilder = XmlProvider.newDocumentBuilder(); + Document document = documentBuilder.parse(new File(getClass().getResource("/document.xml").toURI())); + Element documentElement = document.getDocumentElement(); + + Assert.assertEquals("t:book", documentElement.getTagName()); + Assert.assertEquals(1, documentElement.getElementsByTagName("title").getLength()); + } + + @Test + public void testDocumentWithXmlInclude() throws Exception { + Map properties = new HashMap<>(); + properties.put(XmlProvider.XINCLUDE_AWARE_PROPERTY, true); + properties.put(XmlProvider.NAMESPACE_AWARE_PROPERTY, true); + DocumentBuilder documentBuilder = XmlProvider.newDocumentBuilder(null, properties); + Document document = documentBuilder.parse(new File(XmlProviderTest.class.getResource("/document-with-xinclude.xml").toURI())); + Element documentElement = document.getDocumentElement(); + + Assert.assertEquals("t:book", documentElement.getTagName()); + + if (XmlProvider.isXxeEnabled()) { + Assert.assertEquals(1, documentElement.getElementsByTagName("title").getLength()); + } else { + Assert.assertEquals(0, documentElement.getElementsByTagName("title").getLength()); + } + } + + @Test + public void testSchema() throws Exception { + StreamSource streamSource = new StreamSource(XmlProviderTest.class.getResourceAsStream("/schema.xsd")); + XmlProvider.newSchema(streamSource, null); + } + + @Test + public void testSchemaWithImport() { + StreamSource streamSource = new StreamSource(XmlProviderTest.class.getResourceAsStream("/schema-with-import.xsd")); + + Exception newSchemaException = null; + try { + XmlProvider.newSchema(streamSource, null); + } catch (Exception e) { + newSchemaException = e; + } + + if (XmlProvider.isXxeEnabled()) { + Assert.assertNull(newSchemaException); + } else { + Assert.assertNotNull(newSchemaException); + } + } + + @Test + public void testValidator() throws Exception { + Map properties = new HashMap<>(); + properties.put(XmlProvider.NAMESPACE_AWARE_PROPERTY, true); + DocumentBuilder documentBuilder = XmlProvider.newDocumentBuilder(null, properties); + Document document = documentBuilder.parse(new File(getClass().getResource("/document.xml").toURI())); + Element documentElement = document.getDocumentElement(); + + Validator validator = XmlProvider.newValidator(XmlProviderTest.class.getResource("/schema.xsd")); + validator.validate(new DOMSource(documentElement)); + } + + @Test + public void testValidatorWithImport() throws Exception { + Map properties = new HashMap<>(); + properties.put(XmlProvider.NAMESPACE_AWARE_PROPERTY, true); + DocumentBuilder documentBuilder = XmlProvider.newDocumentBuilder(null, properties); + Document document = documentBuilder.parse(new File(getClass().getResource("/document.xml").toURI())); + Element documentElement = document.getDocumentElement(); + + Exception validateException = null; + try { + Validator validator = XmlProvider.newValidator(XmlProviderTest.class.getResource("/schema-with-import.xsd")); + validator.validate(new DOMSource(documentElement)); + } catch (Exception e) { + validateException = e; + } + + if (XmlProvider.isXxeEnabled()) { + Assert.assertNull(validateException); + } else { + Assert.assertNotNull(validateException); + } + } +} diff --git a/artemis-commons/src/test/resources/document-with-xinclude-title.xml b/artemis-commons/src/test/resources/document-with-xinclude-title.xml new file mode 100644 index 00000000000..f6dca7ce182 --- /dev/null +++ b/artemis-commons/src/test/resources/document-with-xinclude-title.xml @@ -0,0 +1,17 @@ + +document diff --git a/artemis-commons/src/test/resources/document-with-xinclude.xml b/artemis-commons/src/test/resources/document-with-xinclude.xml new file mode 100644 index 00000000000..16aa9438cc5 --- /dev/null +++ b/artemis-commons/src/test/resources/document-with-xinclude.xml @@ -0,0 +1,19 @@ + + + + diff --git a/artemis-commons/src/test/resources/document.xml b/artemis-commons/src/test/resources/document.xml new file mode 100644 index 00000000000..fab35079cf4 --- /dev/null +++ b/artemis-commons/src/test/resources/document.xml @@ -0,0 +1,19 @@ + + + test + diff --git a/artemis-commons/src/test/resources/schema-with-import.xsd b/artemis-commons/src/test/resources/schema-with-import.xsd new file mode 100644 index 00000000000..548562c77a6 --- /dev/null +++ b/artemis-commons/src/test/resources/schema-with-import.xsd @@ -0,0 +1,20 @@ + + + + + diff --git a/artemis-commons/src/test/resources/schema.xsd b/artemis-commons/src/test/resources/schema.xsd new file mode 100644 index 00000000000..f236629e406 --- /dev/null +++ b/artemis-commons/src/test/resources/schema.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/XMLUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/XMLUtil.java index f28b83a9025..fd3c940d391 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/XMLUtil.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/XMLUtil.java @@ -16,12 +16,8 @@ */ package org.apache.activemq.artemis.utils; -import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.dom.DOMSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.InputStream; import java.io.InputStreamReader; @@ -31,6 +27,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; @@ -38,6 +35,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; +import java.util.Map; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -85,10 +84,10 @@ public static String readerToString(final Reader r) throws Exception { } public static Element readerToElement(final Reader r) throws Exception { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setXIncludeAware(true); - DocumentBuilder parser = factory.newDocumentBuilder(); + Map properties = new HashMap<>(); + properties.put(XmlProvider.XINCLUDE_AWARE_PROPERTY, true); + properties.put(XmlProvider.NAMESPACE_AWARE_PROPERTY, true); + DocumentBuilder parser = XmlProvider.newDocumentBuilder(null, properties); Document doc = replaceSystemPropsInXml(parser.parse(new InputSource(new StringReader(replaceSystemPropsInString(readerToString(r)))))); return doc.getDocumentElement(); } @@ -366,10 +365,7 @@ public static double parseDouble(final Node elem) { } public static void validate(final Node node, final String schemaFile) throws Exception { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - - Schema schema = factory.newSchema(new URL(findResource(schemaFile).toURI().toASCIIString())); - Validator validator = schema.newValidator(); + Validator validator = XmlProvider.newValidator(new URL(findResource(schemaFile).toURI().toASCIIString())); // validate the DOM tree try { diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/XmlUtil.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/XmlUtil.java index 8ed76424585..5bcfb0216b5 100644 --- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/XmlUtil.java +++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/XmlUtil.java @@ -16,23 +16,23 @@ */ package org.apache.activemq.artemis.dto; -import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; -import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.util.StreamReaderDelegate; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URI; +import java.util.Collections; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.activemq.artemis.utils.XmlProvider; + public class XmlUtil { /** @@ -73,8 +73,6 @@ public String filter(String str) { } - private static final XMLInputFactory factory = XMLInputFactory.newInstance(); - public static T decode(Class clazz, File configuration) throws Exception { return decode(clazz, configuration, null, null, null); } @@ -90,11 +88,10 @@ public static T decode(Class clazz, JAXBContext jaxbContext = JAXBContext.newInstance("org.apache.activemq.artemis.dto"); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - sf.setFeature("http://apache.org/xml/features/validation/schema-full-checking", false); InputStream xsdStream = XmlUtil.class.getClassLoader().getResourceAsStream("org.apache.activemq/dto/activemq.xsd"); StreamSource xsdSource = new StreamSource(xsdStream); - Schema schema = sf.newSchema(xsdSource); + Schema schema = XmlProvider.newSchema(xsdSource, Collections.singletonMap( + "http://apache.org/xml/features/validation/schema-full-checking", false)); unmarshaller.setSchema(schema); Properties props = new Properties(System.getProperties()); @@ -110,7 +107,7 @@ public static T decode(Class clazz, props.put("artemis.URI.instance", artemisURIInstance.toString()); } - XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream(configuration)); + XMLStreamReader reader = XmlProvider.createXMLStreamReader(new FileInputStream(configuration)); reader = new PropertiesFilter(reader, props); diff --git a/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/XPathExpression.java b/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/XPathExpression.java index d0fe8364d8f..200466f6cdc 100755 --- a/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/XPathExpression.java +++ b/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/XPathExpression.java @@ -17,11 +17,13 @@ package org.apache.activemq.artemis.selector.filter; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import java.util.HashMap; import java.util.Map; import java.util.Properties; +import org.apache.activemq.artemis.utils.XmlProvider; + /** * Used to evaluate an XPath Expression in a JMS selector. */ @@ -36,19 +38,14 @@ public final class XPathExpression implements BooleanExpression { public static final String DOCUMENT_BUILDER_FACTORY_FEATURE_PREFIX = "org.apache.activemq.documentBuilderFactory.feature:"; static { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - try { - factory.setFeature("http://xml.org/sax/features/external-general-entities", false); - factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - - // setup features from system properties (if any) - setupFeatures(factory); - builder = factory.newDocumentBuilder(); + // get features from system properties (if any) + Map features = getFeatures(); + Map properties = new HashMap<>(); + properties.put(XmlProvider.NAMESPACE_AWARE_PROPERTY, true); + properties.put(XmlProvider.IGNORE_COMMENTS_PROPERTY, true); + properties.put(XmlProvider.IGNORE_ELEMENT_CONTENT_WHITESPACE_PROPERTY, true); + builder = XmlProvider.newDocumentBuilder(features, properties); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } @@ -97,14 +94,16 @@ public boolean matches(Filterable message) throws FilterException { return object == Boolean.TRUE; } - protected static void setupFeatures(DocumentBuilderFactory factory) throws ParserConfigurationException { + protected static Map getFeatures() throws ParserConfigurationException { + Map features = new HashMap<>(); Properties properties = System.getProperties(); for (Map.Entry prop : properties.entrySet()) { String key = (String) prop.getKey(); if (key.startsWith(DOCUMENT_BUILDER_FACTORY_FEATURE_PREFIX)) { Boolean value = Boolean.valueOf((String)prop.getValue()); - factory.setFeature(key.substring(DOCUMENT_BUILDER_FACTORY_FEATURE_PREFIX.length()), value); + features.put(key.substring(DOCUMENT_BUILDER_FACTORY_FEATURE_PREFIX.length()), value); } } + return features; } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java index 4abeda8ab13..f41b7702908 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java @@ -16,10 +16,7 @@ */ package org.apache.activemq.artemis.core.deployers.impl; -import javax.xml.XMLConstants; import javax.xml.transform.dom.DOMSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.InputStream; import java.security.AccessController; @@ -110,6 +107,7 @@ import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import org.apache.activemq.artemis.utils.XMLConfigurationUtil; import org.apache.activemq.artemis.utils.XMLUtil; +import org.apache.activemq.artemis.utils.XmlProvider; import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -354,9 +352,7 @@ public void setValidateAIO(final boolean validateAIO) { public Configuration parseMainConfig(final InputStream input) throws Exception { Element e = XMLUtil.streamToElement(input); - SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - Schema schema = schemaFactory.newSchema(XMLUtil.findResource("schema/artemis-server.xsd")); - Validator validator = schema.newValidator(); + Validator validator = XmlProvider.newValidator(XMLUtil.findResource("schema/artemis-server.xsd")); try { validator.validate(new DOMSource(e)); } catch (Exception ex) { diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java index 86a2b347ab4..67ed32c16fb 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java @@ -22,6 +22,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -72,12 +74,16 @@ import org.apache.activemq.artemis.core.settings.impl.SlowConsumerThresholdMeasurementUnit; import org.apache.activemq.artemis.logs.AssertionLoggerHandler; import org.apache.activemq.artemis.utils.RandomUtil; +import org.apache.activemq.artemis.utils.XmlProvider; import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) public class FileConfigurationTest extends ConfigurationImplTest { @BeforeClass @@ -96,6 +102,15 @@ public static void clearProperties() { System.clearProperty("ninetyTwoProp"); } + @Parameterized.Parameters(name = "xxeEnabled={0}") + public static Collection getParameters() { + return Arrays.asList(new Boolean[] {true, false}); + } + + public FileConfigurationTest(boolean xxeEnabled) { + XmlProvider.setXxeEnabled(xxeEnabled); + } + protected String getConfigurationName() { return "ConfigurationTest-full-config.xml"; } diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileXIncludeConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileXIncludeConfigurationTest.java index acd0256eb44..8206ba91f91 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileXIncludeConfigurationTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileXIncludeConfigurationTest.java @@ -17,10 +17,16 @@ package org.apache.activemq.artemis.core.config.impl; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.BeforeClass; public class FileXIncludeConfigurationTest extends FileConfigurationTest { + public FileXIncludeConfigurationTest(boolean xxeEnabled) { + super(xxeEnabled); + Assume.assumeTrue(xxeEnabled); + } + @BeforeClass public static void setupProperties() { System.setProperty("xincludePath", "./src/test/resources"); diff --git a/docs/user-manual/en/configuration-index.md b/docs/user-manual/en/configuration-index.md index 4ff05421721..26a677a0bd4 100644 --- a/docs/user-manual/en/configuration-index.md +++ b/docs/user-manual/en/configuration-index.md @@ -46,6 +46,11 @@ For further information on XInclude see: [https://www.w3.org/TR/xinclude/](https://www.w3.org/TR/xinclude/) +To disable XML external entity processing use the system property `artemis.disableXxe`, e.g.: +``` +-Dartemis.disableXxe=true +``` + ##### Reloading modular configuration files Certain changes in `broker.xml` can be picked up at runtime as discussed in the [Configuration Reload](config-reload.md) diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/XmlExportTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/XmlExportTest.java index a56bc0155dc..2ea08336d3e 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/XmlExportTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/XmlExportTest.java @@ -16,7 +16,6 @@ */ package org.apache.activemq.artemis.tests.integration.amqp; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; @@ -24,6 +23,7 @@ import org.apache.activemq.artemis.cli.commands.tools.xml.XmlDataExporter; import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageSupport; +import org.apache.activemq.artemis.utils.XmlProvider; import org.apache.activemq.transport.amqp.client.AmqpClient; import org.apache.activemq.transport.amqp.client.AmqpConnection; import org.apache.activemq.transport.amqp.client.AmqpMessage; @@ -59,7 +59,7 @@ public void testTextMessage() throws Exception { server.getConfiguration().getPagingDirectory(), server.getConfiguration().getLargeMessagesDirectory()); - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder(). + Document document = XmlProvider.newDocumentBuilder(). parse(new ByteArrayInputStream(xmlOutputStream.toByteArray())); Assert.assertNotNull(XPathFactory.newInstance().newXPath(). diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java index 4ee1de6f8f4..8a0b86e7a16 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java @@ -17,7 +17,6 @@ package org.apache.activemq.artemis.tests.unit.ra; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.lang.reflect.Method; @@ -26,6 +25,7 @@ import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.apache.activemq.artemis.utils.XmlProvider; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -443,8 +443,7 @@ public void testConfiguration() throws Exception { methodList.put(method.getName(), method); } } - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); + DocumentBuilder db = XmlProvider.newDocumentBuilder(); InputStream io = new ByteArrayInputStream(rootConfig.getBytes()); Document dom = db.parse(new InputSource(io));