diff --git a/src/ooxml/java/org/apache/poi/util/DocumentHelper.java b/src/ooxml/java/org/apache/poi/util/DocumentHelper.java new file mode 100644 index 000000000..0246d9a97 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/util/DocumentHelper.java @@ -0,0 +1,122 @@ +/* ==================================================================== + 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.poi.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.events.Namespace; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +public final class DocumentHelper { + private static POILogger logger = POILogFactory.getLogger(DocumentHelper.class); + + private DocumentHelper() {} + + /** + * Creates a new document builder, with sensible defaults + */ + public static synchronized DocumentBuilder newDocumentBuilder() { + try { + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + documentBuilder.setEntityResolver(SAXHelper.IGNORING_ENTITY_RESOLVER); + return documentBuilder; + } catch (ParserConfigurationException e) { + throw new IllegalStateException("cannot create a DocumentBuilder", e); + } + } + + private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + static { + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setValidating(false); + trySetSAXFeature(documentBuilderFactory, SAXHelper.FEATURE_SECURE_PROCESSING, true); + trySetXercesSecurityManager(documentBuilderFactory); + } + + private static void trySetSAXFeature(DocumentBuilderFactory documentBuilderFactory, String feature, boolean enabled) { + try { + documentBuilderFactory.setFeature(feature, enabled); + } catch (Exception e) { + logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e); + } + } + private static void trySetXercesSecurityManager(DocumentBuilderFactory documentBuilderFactory) { + // Try built-in JVM one first, standalone if not + for (String securityManagerClassName : new String[] { + "com.sun.org.apache.xerces.internal.util.SecurityManager", + "org.apache.xerces.util.SecurityManager" + }) { + try { + Object mgr = Class.forName(securityManagerClassName).newInstance(); + Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE); + setLimit.invoke(mgr, 4096); + documentBuilderFactory.setAttribute("http://apache.org/xml/properties/security-manager", mgr); + // Stop once one can be setup without error + return; + } catch (Exception e) { + logger.log(POILogger.INFO, "SAX Security Manager could not be setup", e); + } + } + } + + /** + * Parses the given stream via the default (sensible) + * DocumentBuilder + * @param inp Stream to read the XML data from + * @return the parsed Document + */ + public static Document readDocument(InputStream inp) throws IOException, SAXException { + return newDocumentBuilder().parse(inp); + } + + // must only be used to create empty documents, do not use it for parsing! + private static final DocumentBuilder documentBuilderSingleton = newDocumentBuilder(); + + /** + * Creates a new DOM Document + */ + public static synchronized Document createDocument() { + return documentBuilderSingleton.newDocument(); + } + + /** + * Adds a namespace declaration attribute to the given element. + */ + public static void addNamespaceDeclaration(Element element, String namespacePrefix, String namespaceURI) { + element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, + XMLConstants.XMLNS_ATTRIBUTE + ':' + namespacePrefix, + namespaceURI); + } + + /** + * Adds a namespace declaration attribute to the given element. + */ + public static void addNamespaceDeclaration(Element element, Namespace namespace) { + addNamespaceDeclaration(element, namespace.getPrefix(), namespace.getNamespaceURI()); + } + +} diff --git a/src/ooxml/java/org/apache/poi/util/SAXHelper.java b/src/ooxml/java/org/apache/poi/util/SAXHelper.java index b69225dc8..f2e2ffc11 100644 --- a/src/ooxml/java/org/apache/poi/util/SAXHelper.java +++ b/src/ooxml/java/org/apache/poi/util/SAXHelper.java @@ -25,9 +25,13 @@ import java.lang.reflect.Method; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.io.SAXReader; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; /** @@ -35,34 +39,47 @@ import org.xml.sax.SAXException; */ public final class SAXHelper { private static POILogger logger = POILogFactory.getLogger(SAXHelper.class); - - // remove this constant once on Java 6 and stax-api.jar was removed (which is missing this constant): - private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; - + + private SAXHelper() {} + /** - * Creates a new SAX Reader, with sensible defaults + * Creates a new SAX XMLReader, with sensible defaults */ - public static SAXReader getSAXReader() { - SAXReader xmlReader = new SAXReader(); - xmlReader.setValidation(false); - xmlReader.setEntityResolver(new EntityResolver() { - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException { - return new InputSource(new StringReader("")); - } - }); + public static synchronized XMLReader newXMLReader() throws SAXException, ParserConfigurationException { + XMLReader xmlReader = saxFactory.newSAXParser().getXMLReader(); + xmlReader.setEntityResolver(IGNORING_ENTITY_RESOLVER); trySetSAXFeature(xmlReader, FEATURE_SECURE_PROCESSING, true); trySetXercesSecurityManager(xmlReader); return xmlReader; } - private static void trySetSAXFeature(SAXReader xmlReader, String feature, boolean enabled) { + + static final EntityResolver IGNORING_ENTITY_RESOLVER = new EntityResolver() { + // not in Java 5: @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return new InputSource(new StringReader("")); + } + }; + + private static final SAXParserFactory saxFactory; + static { + saxFactory = SAXParserFactory.newInstance(); + saxFactory.setValidating(false); + saxFactory.setNamespaceAware(true); + } + + // remove this constant once on Java 6 and stax-api.jar was removed (which is missing this constant): + static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; + + private static void trySetSAXFeature(XMLReader xmlReader, String feature, boolean enabled) { try { xmlReader.setFeature(feature, enabled); } catch (Exception e) { logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e); } } - private static void trySetXercesSecurityManager(SAXReader xmlReader) { + + private static void trySetXercesSecurityManager(XMLReader xmlReader) { // Try built-in JVM one first, standalone if not for (String securityManagerClassName : new String[] { "com.sun.org.apache.xerces.internal.util.SecurityManager", @@ -81,6 +98,21 @@ public final class SAXHelper { } } + /** + * Creates a new DOM4J SAXReader, with sensible defaults + */ + public static SAXReader getSAXReader() throws DocumentException { + try { + SAXReader reader = new SAXReader(newXMLReader(), false); + reader.setEntityResolver(IGNORING_ENTITY_RESOLVER); + return reader; + } catch (SAXException saxe) { + throw new DocumentException(saxe); + } catch (ParserConfigurationException pce) { + throw new DocumentException(pce); + } + } + /** * Parses the given stream via the default (sensible) * SAX Reader diff --git a/src/ooxml/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java b/src/ooxml/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java index 8a4b139ff..97bfe47f0 100644 --- a/src/ooxml/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java @@ -22,12 +22,11 @@ import java.util.ArrayList; import java.util.List; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.SAXHelper; import org.apache.poi.xssf.usermodel.XSSFRelation; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst; import org.xml.sax.Attributes; @@ -135,10 +134,8 @@ public class ReadOnlySharedStringsTable extends DefaultHandler { */ public void readFrom(InputStream is) throws IOException, SAXException { InputSource sheetSource = new InputSource(is); - SAXParserFactory saxFactory = SAXParserFactory.newInstance(); try { - SAXParser saxParser = saxFactory.newSAXParser(); - XMLReader sheetParser = saxParser.getXMLReader(); + XMLReader sheetParser = SAXHelper.newXMLReader(); sheetParser.setContentHandler(this); sheetParser.parse(sheetSource); } catch(ParserConfigurationException e) { diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java index a313271e5..5771964b0 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java @@ -22,8 +22,6 @@ import java.util.List; import java.util.Locale; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import org.apache.poi.POIXMLProperties; import org.apache.poi.POIXMLTextExtractor; @@ -33,6 +31,7 @@ import org.apache.poi.POIXMLProperties.ExtendedProperties; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.util.SAXHelper; import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; @@ -154,10 +153,8 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor { } InputSource sheetSource = new InputSource(sheetInputStream); - SAXParserFactory saxFactory = SAXParserFactory.newInstance(); try { - SAXParser saxParser = saxFactory.newSAXParser(); - XMLReader sheetParser = saxParser.getXMLReader(); + XMLReader sheetParser = SAXHelper.newXMLReader(); ContentHandler handler = new XSSFSheetXMLHandler( styles, strings, sheetContentsExtractor, formatter, formulasNotResults); sheetParser.setContentHandler(handler); diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java index aa68c2638..cd0307fc1 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java @@ -26,8 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Vector; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; @@ -41,6 +39,7 @@ import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.util.DocumentHelper; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFMap; import org.apache.poi.xssf.usermodel.XSSFRow; @@ -101,15 +100,6 @@ public class XSSFExportToXml implements Comparator{ exportToXML(os, "UTF-8", validate); } - private Document getEmptyDocument() throws ParserConfigurationException{ - - DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); - Document doc = docBuilder.newDocument(); - - return doc; - } - /** * Exports the data in an XML stream * @@ -127,7 +117,7 @@ public class XSSFExportToXml implements Comparator{ String rootElement = map.getCtMap().getRootElement(); - Document doc = getEmptyDocument(); + Document doc = DocumentHelper.createDocument(); Element root = null; diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java index e87a0333b..498f4dfff 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java @@ -24,13 +24,13 @@ import java.util.List; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.xssf.usermodel.XSSFTable; @@ -75,11 +75,9 @@ public class XSSFImportFromXML { * @throws ParserConfigurationException if there are problems with XML parser configuration * @throws IOException if there are problems reading the input string */ - public void importFromXML(String xmlInputString) throws SAXException, XPathExpressionException, ParserConfigurationException, IOException { + public void importFromXML(String xmlInputString) throws SAXException, XPathExpressionException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); + DocumentBuilder builder = DocumentHelper.newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(xmlInputString.trim())));