/*
* 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;
}
}