/* * Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed 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.wso2.carbon.humantask.core.utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xerces.dom.DOMOutputImpl; import org.apache.xerces.impl.Constants; import org.apache.xerces.util.SecurityManager; import org.apache.xml.serialize.DOMSerializerImpl; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * DOM Utility Methods */ public final class DOMUtils { private static ThreadLocal<DocumentBuilder> builders = new ThreadLocal(); private static final DocumentBuilderFactory documentBuilderFactory ; private static Log log = LogFactory.getLog(DOMUtils.class); public static final String NS_URI_XMLNS = "http://www.w3.org/2000/xmlns/"; private static final int ENTITY_EXPANSION_LIMIT = 0; static { documentBuilderFactory = getSecuredDocumentBuilder(); } private DOMUtils() { } /** * Initialize the document-builder factory. documentBuilderFactory = f; */ public static Document newDocument() { DocumentBuilder db = getBuilder(); return db.newDocument(); } private static DocumentBuilder getBuilder() { DocumentBuilder builder = builders.get(); if (builder == null) { synchronized (documentBuilderFactory) { try { builder = documentBuilderFactory.newDocumentBuilder(); builder.setErrorHandler(new SAXLoggingErrorHandler()); } catch (ParserConfigurationException e) { log.error(e.getMessage(), e); throw new RuntimeException(e); } } builders.set(builder); } return builder; } /** * Convert a DOM node to a stringified XML representation. * * @param node DOM Node * @return String */ public static String domToString(Node node) { if (node == null) { throw new IllegalArgumentException("Cannot stringify null Node!"); } String value; short nodeType = node.getNodeType(); if (nodeType == Node.ELEMENT_NODE || nodeType == Node.DOCUMENT_NODE) { // serializer doesn't handle Node type well, only Element DOMSerializerImpl ser = new DOMSerializerImpl(); ser.setParameter(Constants.DOM_NAMESPACES, Boolean.TRUE); ser.setParameter(Constants.DOM_WELLFORMED, Boolean.FALSE); ser.setParameter(Constants.DOM_VALIDATE, Boolean.FALSE); // create a proper XML encoding header based on the input document; // default to UTF-8 if the parent document's encoding is not accessible String usedEncoding = "UTF-8"; Document parent = node.getOwnerDocument(); if (parent != null) { String parentEncoding = parent.getXmlEncoding(); if (parentEncoding != null) { usedEncoding = parentEncoding; } } // the receiver of the DOM DOMOutputImpl out = new DOMOutputImpl(); out.setEncoding(usedEncoding); // we write into a String StringWriter writer = new StringWriter(4096); out.setCharacterStream(writer); // out, ye characters! ser.write(node, out); writer.flush(); // finally get the String value = writer.toString(); } else { value = node.getNodeValue(); } return value; } /** * Parse a String into a DOM. * * @param s DOCUMENTME * @return DOCUMENTME * @throws org.xml.sax.SAXException DOCUMENTME * @throws java.io.IOException DOCUMENTME */ public static Element stringToDOM(String s) throws SAXException, IOException { return parse(new InputSource(new StringReader(s))).getDocumentElement(); } /** * Parse an XML document located using an {@link org.xml.sax.InputSource} using the * pooled document builder. * * @param inputSource Input Source to parse * @return Parsed document * @throws java.io.IOException If an error occurred while reading from the input source * @throws org.xml.sax.SAXException if the content in the input source is invalid */ public static Document parse(InputSource inputSource) throws SAXException, IOException { DocumentBuilder db = getBuilder(); // Findbug false positive XXE_DOCUMENT warning. Here we are taking Secured DocumentBuilder factory to build // DocumentBuilder. return db.parse(inputSource); } // public static Element findChildByName(Element parent, QName name) { // return findChildByName(parent, name, false); // } // public static Element findChildByName(Element parent, QName name, boolean recurse) { // if (parent == null) { // throw new IllegalArgumentException("null parent"); // } // if (name == null) { // throw new IllegalArgumentException("null name"); // } // // NodeList nl = parent.getChildNodes(); // for (int i = 0; i < nl.getLength(); ++i) { // Node c = nl.item(i); // if(c.getNodeType() != Node.ELEMENT_NODE) { // continue; // } // // For a reason that I can't fathom, when using in-mem DAO we actually get elements with // // no localname. // String nodeName = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); // if (new QName(c.getNamespaceURI(),nodeName).equals(name)) { // return (Element) c; // } // } // // if(recurse){ // NodeList cnl = parent.getChildNodes(); // for (int i = 0; i < cnl.getLength(); ++i) { // Node c = cnl.item(i); // if(c.getNodeType() != Node.ELEMENT_NODE) { // continue; // } // Element result = findChildByName((Element)c, name, recurse); // if(result != null) { // return result; // } // } // } // return null; // } // public static void copyNSContext(Element source, Element dest) { // Map<String, String> sourceNS = getParentNamespaces(source); // sourceNS.putAll(getMyNamespaces(source)); // Map<String, String> destNS = getParentNamespaces(dest); // destNS.putAll(getMyNamespaces(dest)); // // (source - dest) to avoid adding twice the same ns on dest // for (String pr : destNS.keySet()) sourceNS.remove(pr); // // for (Map.Entry<String, String> entry : sourceNS.entrySet()) { // String prefix = entry.getKey(); // String uri = entry.getValue(); // if (prefix == null || "".equals(prefix)) // dest.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns", uri); // else // dest.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns:"+ prefix, uri); // } // } // // /** // * This method traverses the DOM and grabs namespace declarations // * on parent elements with the intent of preserving them for children. <em>Note // * that the DOM level 3 document method {@link Element#getAttribute(java.lang.String)} // * is not desirable in this case, as it does not respect namespace prefix // * bindings that may affect attribute values. (Namespaces in DOM are // * uncategorically a mess, especially in the context of XML Schema.)</em> // * @param el the starting element // * @return a {@link Map} containing prefix bindings. // */ // public static Map<String, String> getParentNamespaces(Element el) { // HashMap<String,String> pref = new HashMap<String,String>(); // Map<String,String> mine = getMyNamespaces(el); // Node n = el.getParentNode(); // while (n != null && n.getNodeType() != Node.DOCUMENT_NODE) { // if (n instanceof Element) { // Element l = (Element) n; // NamedNodeMap nnm = l.getAttributes(); // int len = nnm.getLength(); // for (int i = 0; i < len; ++i) { // Attr a = (Attr) nnm.item(i); // if (isNSAttribute(a)) { // String key = getNSPrefixFromNSAttr(a); // String uri = a.getValue(); // // prefer prefix bindings that are lower down in the tree. // if (pref.containsKey(key) || mine.containsKey(key)) continue; // pref.put(key, uri); // } // } // } // n = n.getParentNode(); // } // return pref; // } // // public static Map<String,String> getMyNamespaces(Element el) { // HashMap<String,String> mine = new HashMap<String,String>(); // NamedNodeMap nnm = el.getAttributes(); // int len = nnm.getLength(); // for (int i=0; i < len; ++i) { // Attr a = (Attr) nnm.item(i); // if (isNSAttribute(a)) { // mine.put(getNSPrefixFromNSAttr(a),a.getValue()); // } // } // return mine; // } // // /** // * Test whether an attribute contains a namespace declaration. // * @param a an {@link Attr} to test. // * @return <code>true</code> if the {@link Attr} is a namespace declaration // */ // public static boolean isNSAttribute(Attr a) { // assert a != null; // String s = a.getNamespaceURI(); // return (s != null && s.equals(NS_URI_XMLNS)); // } // // /** // * Fetch the non-null namespace prefix from a {@link Attr} that declares // * a namespace. (The DOM APIs will return <code>null</code> for a non-prefixed // * declaration. // * @param a the {@link Attr} with the declaration (must be non-<code>null</code). // * @return the namespace prefix or <code>""</code> if none was // * declared, e.g., <code>xmlns="foo"</code>. // */ // public static String getNSPrefixFromNSAttr(Attr a) { // assert a != null; // assert isNSAttribute(a); // if (a.getPrefix() == null) { // return ""; // } // return a.getName().substring(a.getPrefix().length()+1); // } // public static Element findChildElement(Element element) { // return (Element)findChildNode(element, Node.ELEMENT_NODE, false); // } // public static Node findChildNode(Element parent, short nodeType, boolean recurse) { // if (parent == null) { // throw new IllegalArgumentException("null parent"); // } // // NodeList nl = parent.getChildNodes(); // for (int i = 0; i < nl.getLength(); ++i) { // Node c = nl.item(i); // if(c.getNodeType() != nodeType) { // continue; // } // return c; // } // // if(recurse){ // NodeList cnl = parent.getChildNodes(); // for (int i = 0; i < cnl.getLength(); ++i) { // Node c = cnl.item(i); // if(c.getNodeType() != Node.ELEMENT_NODE) { // continue; // } // Node result = findChildNode((Element)c, nodeType, recurse); // if(result != null) { // return result; // } // } // } // return null; // } // We added this method as a dummy because we need to test the functionality. // We have an issue right now due to xsd:anyType usage returning Objects. //This method creates an element if the object cannot be cast to type Element. public static Element getElementFromObject(Object data) { Element dataElement = null; try { dataElement = (Element) data; } catch (Exception e) { log.warn("Object is not an Element. Trying to create the Element explicitly.", e); try { //We need a Document DocumentBuilderFactory dbfac = getSecuredDocumentBuilder(); DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document doc = docBuilder.newDocument(); //////////////////////// //Creating the XML tree //create the root element and add it to the document dataElement = doc.createElement("data"); doc.appendChild(dataElement); //add a text element to the child Text text = doc.createTextNode("no data provided"); if (data != null) { text = doc.createTextNode(data.toString()); } dataElement.appendChild(text); } catch (Exception ex) { log.error("Error while creating the element.", ex); } } return dataElement; } /** * Create DocumentBuilderFactory with the XXE and XEE prevention measurements. * * @return DocumentBuilderFactory instance */ public static DocumentBuilderFactory getSecuredDocumentBuilder() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); try { dbf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE, false); dbf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE, false); dbf.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false); dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); } catch (ParserConfigurationException e) { log.error("Failed to load XML Processor Feature " + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE + " or " + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE + " or " + Constants.LOAD_EXTERNAL_DTD_FEATURE + " or secure-processing."); } SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT); dbf.setAttribute(Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY, securityManager); return dbf; } }