diff --git a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestBulkWriteWithTransformations.java b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestBulkWriteWithTransformations.java index e6a90bd8d..38cf4bc2d 100644 --- a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestBulkWriteWithTransformations.java +++ b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestBulkWriteWithTransformations.java @@ -8,6 +8,7 @@ import com.marklogic.client.admin.ExtensionMetadata; import com.marklogic.client.admin.TransformExtensionsManager; import com.marklogic.client.document.*; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.DOMHandle; import com.marklogic.client.io.FileHandle; import com.marklogic.client.io.SourceHandle; @@ -74,8 +75,7 @@ public void testBulkLoadWithXSLTClientSideTransform() throws KeyManagementExcept // get the xslt Source xsl = new StreamSource("src/test/java/com/marklogic/client/functionaltest/data/employee-stylesheet.xsl"); - // create transformer - TransformerFactory factory = TransformerFactory.newInstance(); + TransformerFactory factory = XmlFactories.makeNewTransformerFactory(); Transformer transformer = factory.newTransformer(xsl); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); diff --git a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestDocumentEncoding.java b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestDocumentEncoding.java index 86a5915c9..5240c42c5 100644 --- a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestDocumentEncoding.java +++ b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestDocumentEncoding.java @@ -6,10 +6,10 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.document.XMLDocumentManager; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.BytesHandle; import com.marklogic.client.io.StringHandle; -import org.junit.jupiter.api.*; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -50,7 +50,7 @@ public void testEncoding() throws KeyManagementException, NoSuchAlgorithmExcepti // transform the Document into a String Source domSource = new DOMSource(doc); - TransformerFactory tf = TransformerFactory.newInstance(); + TransformerFactory tf = XmlFactories.makeNewTransformerFactory(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.ENCODING, "Cp1252"); @@ -88,7 +88,7 @@ public void testEncoding() throws KeyManagementException, NoSuchAlgorithmExcepti x2.appendChild(text2); Source domSource2 = new DOMSource(doc2); - TransformerFactory tf2 = TransformerFactory.newInstance(); + TransformerFactory tf2 = XmlFactories.makeNewTransformerFactory(); Transformer transformer2 = tf2.newTransformer(); transformer2.setOutputProperty(OutputKeys.METHOD, "xml"); transformer2.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); diff --git a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestTransformXMLWithXSLT.java b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestTransformXMLWithXSLT.java index f3d524197..43892285b 100644 --- a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestTransformXMLWithXSLT.java +++ b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/fastfunctest/TestTransformXMLWithXSLT.java @@ -6,10 +6,10 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.document.XMLDocumentManager; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.FileHandle; import com.marklogic.client.io.SourceHandle; -import org.junit.jupiter.api.*; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import javax.xml.transform.*; import javax.xml.transform.stream.StreamSource; @@ -17,6 +17,8 @@ import java.io.FileNotFoundException; import java.util.Scanner; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class TestTransformXMLWithXSLT extends AbstractFunctionalTest { @@ -31,8 +33,7 @@ public void testWriteXMLWithXSLTransform() throws TransformerException, FileNotF // get the xslt Source xsl = new StreamSource("src/test/java/com/marklogic/client/functionaltest/data/employee-stylesheet.xsl"); - // create transformer - TransformerFactory factory = TransformerFactory.newInstance(); + TransformerFactory factory = XmlFactories.makeNewTransformerFactory(); Transformer transformer = factory.newTransformer(xsl); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); diff --git a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/BasicJavaClientREST.java b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/BasicJavaClientREST.java index dc24ff03f..af4d0beca 100644 --- a/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/BasicJavaClientREST.java +++ b/marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/BasicJavaClientREST.java @@ -14,6 +14,7 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.admin.QueryOptionsManager; import com.marklogic.client.document.DocumentManager; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.*; import com.marklogic.client.io.DocumentMetadataHandle.DocumentCollections; import com.marklogic.client.io.DocumentMetadataHandle.DocumentPermissions; @@ -1476,7 +1477,7 @@ public String convertXMLStreamReaderToString(XMLStreamReader reader) throws XMLS */ public String convertXMLDocumentToString(Document readContent) throws TransformerException { - TransformerFactory tf = TransformerFactory.newInstance(); + TransformerFactory tf = XmlFactories.makeNewTransformerFactory(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter writer = new StringWriter(); @@ -1514,10 +1515,10 @@ public String convertInputStreamToString(InputStream fileRead) throws IOExceptio * @throws IOException * @throws TransformerException */ - public String convertInputSourceToString(InputSource fileRead) throws IOException, TransformerException + public String convertInputSourceToString(InputSource fileRead) throws TransformerException { Source saxsrc = new SAXSource(fileRead); - TransformerFactory tf = TransformerFactory.newInstance(); + TransformerFactory tf = XmlFactories.makeNewTransformerFactory(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter writer = new StringWriter(); @@ -1538,7 +1539,7 @@ public String convertSourceToString(Source reader) throws TransformerException { StringWriter stringWriter = new StringWriter(); Result result = new StreamResult(stringWriter); - TransformerFactory factory = TransformerFactory.newInstance(); + TransformerFactory factory = XmlFactories.makeNewTransformerFactory(); Transformer transformer = factory.newTransformer(); transformer.transform(reader, result); String str = stringWriter.getBuffer().toString(); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/XmlFactories.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/XmlFactories.java index 6937ac27c..c859da6bb 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/XmlFactories.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/XmlFactories.java @@ -3,17 +3,21 @@ */ package com.marklogic.client.impl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.xml.XMLConstants; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; import java.lang.ref.SoftReference; public final class XmlFactories { + private static final Logger logger = LoggerFactory.getLogger(XmlFactories.class); + private static final CachedInstancePerThreadSupplier cachedOutputFactory = new CachedInstancePerThreadSupplier(new Supplier() { @Override @@ -51,13 +55,25 @@ public static XMLInputFactory makeNewInputFactory() { return factory; } - public static TransformerFactory makeNewTransformerFactory() throws TransformerConfigurationException { + public static TransformerFactory makeNewTransformerFactory() { TransformerFactory factory = TransformerFactory.newInstance(); // Avoids Polaris warning related to https://cwe.mitre.org/data/definitions/611.html . // From https://stackoverflow.com/questions/32178558/how-to-prevent-xml-external-entity-injection-on-transformerfactory . - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + try { + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (TransformerConfigurationException e) { + logger.warn("Unable to set {} on TransformerFactory; cause: {}", XMLConstants.FEATURE_SECURE_PROCESSING, e.getMessage()); + } + try { + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (IllegalArgumentException e) { + logger.warn("Unable to set {} on TransformerFactory; cause: {}", XMLConstants.ACCESS_EXTERNAL_DTD, e.getMessage()); + } + try { + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + } catch (IllegalArgumentException e) { + logger.warn("Unable to set {} on TransformerFactory; cause: {}", XMLConstants.ACCESS_EXTERNAL_STYLESHEET, e.getMessage()); + } return factory; } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java index 2538a0401..818693253 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java @@ -3,34 +3,24 @@ */ package com.marklogic.client.io; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; +import com.marklogic.client.MarkLogicIOException; +import com.marklogic.client.MarkLogicInternalException; +import com.marklogic.client.impl.XmlFactories; +import com.marklogic.client.io.marker.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.*; import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; -import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.validation.Schema; - -import com.marklogic.client.io.marker.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.*; - -import com.marklogic.client.MarkLogicIOException; -import com.marklogic.client.MarkLogicInternalException; +import java.io.*; +import java.nio.charset.StandardCharsets; /** *

An Input Source Handle represents XML content as an input source for reading or writing. @@ -413,12 +403,12 @@ protected OutputStreamSender sendContent() { } protected OutputStreamSender sendContent(InputSource content) { return (content == null) ? null : - new OutputStreamSenderImpl(makeTransformer(), makeReader(true), content); + new OutputStreamSenderImpl(makeTransformerFactory(), makeReader(true), content); } @Override public void write(OutputStream out) throws IOException { try { - makeTransformer().newTransformer().transform( + makeTransformerFactory().newTransformer().transform( new SAXSource(makeReader(true), content), new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)) ); @@ -427,20 +417,8 @@ public void write(OutputStream out) throws IOException { throw new MarkLogicIOException(e); } } - private TransformerFactory makeTransformer() { - TransformerFactory factory = TransformerFactory.newInstance(); - // default to best practices for conservative security including recommendations per - // https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md - try { - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - } catch (TransformerConfigurationException e) {} - try { - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } catch (IllegalArgumentException e) {} - try { - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); - } catch (IllegalArgumentException e) {} - return factory; + private TransformerFactory makeTransformerFactory() { + return XmlFactories.makeNewTransformerFactory(); } /** diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java index 27f550e1f..4e3bee1f9 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java @@ -3,27 +3,17 @@ */ package com.marklogic.client.io; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; - -import javax.xml.XMLConstants; -import javax.xml.transform.*; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - +import com.marklogic.client.MarkLogicIOException; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.marker.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.marklogic.client.MarkLogicIOException; +import javax.xml.transform.*; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.*; +import java.nio.charset.StandardCharsets; /** *

A Source Handle represents XML content as a transform source for reading @@ -177,7 +167,7 @@ public void transform(Result result) { } else { if (logger.isWarnEnabled()) logger.warn("No transformer, so using identity transform"); - transformer = makeTransformer().newTransformer(); + transformer = makeTransformerFactory().newTransformer(); } transformer.transform(content, result); @@ -186,20 +176,8 @@ public void transform(Result result) { throw new MarkLogicIOException(e); } } - private TransformerFactory makeTransformer() { - TransformerFactory factory = TransformerFactory.newInstance(); - // default to best practices for conservative security including recommendations per - // https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md - try { - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - } catch (TransformerConfigurationException e) {} - try { - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } catch (IllegalArgumentException e) {} - try { - factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); - } catch (IllegalArgumentException e) {} - return factory; + private TransformerFactory makeTransformerFactory() { + return XmlFactories.makeNewTransformerFactory(); } /** @@ -260,7 +238,7 @@ public byte[] contentToBytes(Source content) { if (content == null) return null; try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - makeTransformer().newTransformer().transform(content, + makeTransformerFactory().newTransformer().transform(content, new StreamResult(new OutputStreamWriter(buffer, StandardCharsets.UTF_8))); return buffer.toByteArray(); } catch (TransformerException e) { diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/HandleAsTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/HandleAsTest.java index 7c9a9edfa..d15f70e0c 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/HandleAsTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/HandleAsTest.java @@ -8,6 +8,7 @@ import com.marklogic.client.document.BinaryDocumentManager; import com.marklogic.client.document.TextDocumentManager; import com.marklogic.client.document.XMLDocumentManager; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.BaseHandle; import com.marklogic.client.io.Format; import com.marklogic.client.io.JAXBHandle; @@ -18,6 +19,8 @@ import com.marklogic.client.query.MatchDocumentSummary; import com.marklogic.client.query.QueryDefinition; import com.marklogic.client.query.QueryManager; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.annotation.XmlRootElement; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -27,8 +30,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.annotation.XmlRootElement; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -36,7 +37,10 @@ import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import javax.xml.transform.*; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import java.io.*; @@ -144,7 +148,7 @@ public void testBuiltinReadWrite() xmlMgr.delete(xmlDocId); assertEquals(beforeText, afterText); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); + Transformer transformer = XmlFactories.makeNewTransformerFactory().newTransformer(); xmlMgr.writeAs(xmlDocId, new DOMSource(beforeDocument)); DOMResult afterResult = new DOMResult(); diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/XMLDocumentTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/XMLDocumentTest.java index ea1928fd5..dd0d72aed 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/XMLDocumentTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/XMLDocumentTest.java @@ -11,6 +11,7 @@ import com.marklogic.client.document.DocumentPatchBuilder.Position; import com.marklogic.client.document.XMLDocumentManager; import com.marklogic.client.document.XMLDocumentManager.DocumentRepair; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.*; import com.marklogic.client.io.marker.DocumentPatchHandle; import com.marklogic.client.util.EditableNamespaceContext; @@ -36,7 +37,6 @@ import javax.xml.stream.events.XMLEvent; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; @@ -107,7 +107,7 @@ public void testReadWrite() String docId2 = "/test/testWrite2.xml"; - Transformer transformer = TransformerFactory.newInstance().newTransformer(); + Transformer transformer = XmlFactories.makeNewTransformerFactory().newTransformer(); SourceHandle sourceHandle = new SourceHandle(); sourceHandle.setTransformer(transformer); docMgr.write(docId2, docMgr.read(docId, sourceHandle)); diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/RowManagerTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/RowManagerTest.java index 36e86899f..894204f57 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/RowManagerTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/RowManagerTest.java @@ -11,6 +11,7 @@ import com.marklogic.client.datamovement.WriteBatcher; import com.marklogic.client.document.DocumentManager; import com.marklogic.client.expression.PlanBuilder; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.*; import com.marklogic.client.query.DeleteQueryDefinition; import com.marklogic.client.query.QueryManager; @@ -1790,7 +1791,7 @@ private void checkRecordDocRows(RowSet rowSet) throws SAXException, I private String nodeToString(Node node) throws TransformerException, TransformerFactoryConfigurationError { StringWriter sw = new StringWriter(); - Transformer tf = TransformerFactory.newInstance().newTransformer(); + Transformer tf = XmlFactories.makeNewTransformerFactory().newTransformer(); tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); tf.transform( new DOMSource(node), new StreamResult(sw)