/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * This product combines work with different licenses. See the "NOTICE" text file for details on the * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative * works that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egiz.eaaf.core.impl.utils; import java.util.List; import java.util.Map; import org.jaxen.JaxenException; import org.jaxen.NamespaceContext; import org.jaxen.Navigator; import org.jaxen.SimpleNamespaceContext; import org.jaxen.dom.DOMXPath; import org.jaxen.dom.DocumentNavigator; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.traversal.NodeIterator; import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; import at.gv.egiz.eaaf.core.exceptions.XPathException; /** * Utility methods to evaluate XPath expressions on DOM nodes. * * @author Patrick Peck * @version $Id$ */ public class XPathUtils { /** * The XPath expression selecting all nodes under a given root (including the * root node itself). */ public static final String ALL_NODES_XPATH = "(.//. | .//@* | .//namespace::*)"; /** The DocumentNavigator to use for navigating the document. */ private static Navigator documentNavigator = DocumentNavigator.getInstance(); /** The default namespace prefix to namespace URI mappings. */ private static NamespaceContext NS_CONTEXT; static { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(); ctx.addNamespace(XmlNamespaceConstants.MOA_PREFIX, XmlNamespaceConstants.MOA_NS_URI); ctx.addNamespace(XmlNamespaceConstants.MOA_CONFIG_PREFIX, XmlNamespaceConstants.MOA_CONFIG_NS_URI); ctx.addNamespace(XmlNamespaceConstants.MOA_ID_CONFIG_PREFIX, XmlNamespaceConstants.MOA_ID_CONFIG_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SL10_PREFIX, XmlNamespaceConstants.SL10_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SL11_PREFIX, XmlNamespaceConstants.SL11_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SL12_PREFIX, XmlNamespaceConstants.SL12_NS_URI); ctx.addNamespace(XmlNamespaceConstants.ECDSA_PREFIX, XmlNamespaceConstants.ECDSA_NS_URI); ctx.addNamespace(XmlNamespaceConstants.PD_PREFIX, XmlNamespaceConstants.PD_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SAML_PREFIX, XmlNamespaceConstants.SAML_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SAMLP_PREFIX, XmlNamespaceConstants.SAMLP_NS_URI); ctx.addNamespace(XmlNamespaceConstants.DSIG_PREFIX, XmlNamespaceConstants.DSIG_NS_URI); ctx.addNamespace(XmlNamespaceConstants.XSLT_PREFIX, XmlNamespaceConstants.XSLT_NS_URI); ctx.addNamespace(XmlNamespaceConstants.XSI_PREFIX, XmlNamespaceConstants.XSI_NS_URI); ctx.addNamespace(XmlNamespaceConstants.DSIG_FILTER2_PREFIX, XmlNamespaceConstants.DSIG_FILTER2_NS_URI); ctx.addNamespace(XmlNamespaceConstants.DSIG_EC_PREFIX, XmlNamespaceConstants.DSIG_EC_NS_URI); ctx.addNamespace(XmlNamespaceConstants.MD_PREFIX, XmlNamespaceConstants.MD_NS_URI); ctx.addNamespace(XmlNamespaceConstants.MDP_PREFIX, XmlNamespaceConstants.MDP_NS_URI); ctx.addNamespace(XmlNamespaceConstants.MVV_PREFIX, XmlNamespaceConstants.MVV_NS_URI); ctx.addNamespace(XmlNamespaceConstants.STB_PREFIX, XmlNamespaceConstants.STB_NS_URI); ctx.addNamespace(XmlNamespaceConstants.WRR_PREFIX, XmlNamespaceConstants.WRR_NS_URI); ctx.addNamespace(XmlNamespaceConstants.STORK_PREFIX, XmlNamespaceConstants.STORK_NS_URI); ctx.addNamespace(XmlNamespaceConstants.STORKP_PREFIX, XmlNamespaceConstants.STORKP_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SAML2_PREFIX, XmlNamespaceConstants.SAML2_NS_URI); ctx.addNamespace(XmlNamespaceConstants.SAML2P_PREFIX, XmlNamespaceConstants.SAML2P_NS_URI); ctx.addNamespace(XmlNamespaceConstants.XENC_PREFIX, XmlNamespaceConstants.XENC_NS_URI); ctx.addNamespace(XmlNamespaceConstants.XADES_1_1_1_NS_PREFIX, XmlNamespaceConstants.XADES_1_1_1_NS_URI); NS_CONTEXT = ctx; } /** * Return a NodeIterator over the nodes matching the XPath * expression. * *

* All namespace URIs and prefixes declared in the Constants * interface are used for resolving namespaces. *

* * @param contextNode The root node from which to evaluate the XPath expression. * @param exp The XPath expression to evaluate. * @return An iterator over the resulting nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeIterator selectNodeIterator(final Node contextNode, final String exp) throws XPathException { return selectNodeIterator(contextNode, NS_CONTEXT, exp); } /** * Return a NodeIterator over the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceElement An element from which to build the namespace mapping * for evaluating the XPath expression * @param exp The XPath expression to evaluate. * @return An iterator over the resulting nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeIterator selectNodeIterator(final Node contextNode, final Element namespaceElement, final String exp) throws XPathException { try { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(); ctx.addElementNamespaces(documentNavigator, namespaceElement); return selectNodeIterator(contextNode, ctx, exp); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Return a NodeIterator over the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceMapping A namespace prefix to namespace URI mapping * (String to String) for * evaluating the XPath expression. * @param exp The XPath expression to evaluate. * @return An iterator over the resulting nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeIterator selectNodeIterator(final Node contextNode, final Map namespaceMapping, final String exp) throws XPathException { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping); return selectNodeIterator(contextNode, ctx, exp); } /** * Return a NodeIterator over the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath expression. * @param nsContext The NamespaceContext for resolving namespace * prefixes to namespace URIs for evaluating the XPath * expression. * @param exp The XPath expression to evaluate. * @return An iterator over the resulting nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ private static NodeIterator selectNodeIterator(final Node contextNode, final NamespaceContext nsContext, final String exp) throws XPathException { try { final DOMXPath xpath = new DOMXPath(exp); List nodes; xpath.setNamespaceContext(nsContext); nodes = xpath.selectNodes(contextNode); return new NodeIteratorAdapter(nodes.listIterator()); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Return a NodeList of all the nodes matching the XPath * expression. *

* All namespace URIs and prefixes declared in the Constants * interface are used for resolving namespaces. *

* * @param contextNode The root node from which to evaluate the XPath expression. * @param exp The XPath expression to evaluate. * @return A NodeList containing the matching nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeList selectNodeList(final Node contextNode, final String exp) throws XPathException { return selectNodeList(contextNode, NS_CONTEXT, exp); } /** * Return a NodeList of all the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceElement An element from which to build the namespace mapping * for evaluating the XPath expression * @param exp The XPath expression to evaluate. * @return A NodeList containing the matching nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeList selectNodeList(final Node contextNode, final Element namespaceElement, final String exp) throws XPathException { try { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(); ctx.addElementNamespaces(documentNavigator, namespaceElement); return selectNodeList(contextNode, ctx, exp); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Return a NodeList of all the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceMapping A namespace prefix to namespace URI mapping * (String to String) for * evaluating the XPath expression. * @param exp The XPath expression to evaluate. * @return A NodeList containing the matching nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ public static NodeList selectNodeList(final Node contextNode, final Map namespaceMapping, final String exp) throws XPathException { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping); return selectNodeList(contextNode, ctx, exp); } /** * Return a NodeList of all the nodes matching the XPath * expression. * * @param contextNode The root node from which to evaluate the XPath expression. * @param nsContext The NamespaceContext for resolving namespace * prefixes to namespace URIs for evaluating the XPath * expression. * @param exp The XPath expression to evaluate. * @return A NodeList containing the matching nodes. * @throws XPathException An error occurred evaluating the XPath expression. */ private static NodeList selectNodeList(final Node contextNode, final NamespaceContext nsContext, final String exp) throws XPathException { try { final DOMXPath xpath = new DOMXPath(exp); List nodes; xpath.setNamespaceContext(nsContext); nodes = xpath.selectNodes(contextNode); return new NodeListAdapter(nodes); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Select the first node matching an XPath expression. *

* All namespace URIs and prefixes declared in the Constants * interface are used for resolving namespaces. *

* * @param contextNode The root node from which to evaluate the XPath expression. * @param exp The XPath expression to evaluate. * @return Node The first node matching the XPath expression, or * null, if no node matched. * @throws XPathException An error occurred evaluating the XPath expression. */ public static Node selectSingleNode(final Node contextNode, final String exp) throws XPathException { return selectSingleNode(contextNode, NS_CONTEXT, exp); } /** * Select the first node matching an XPath expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceElement An element from which to build the namespace mapping * for evaluating the XPath expression * @param exp The XPath expression to evaluate. * @return Node The first node matching the XPath expression, or * null, if no node matched. * @throws XPathException An error occurred evaluating the XPath expression. */ public static Node selectSingleNode(final Node contextNode, final Element namespaceElement, final String exp) throws XPathException { try { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(); ctx.addElementNamespaces(documentNavigator, namespaceElement); return selectSingleNode(contextNode, ctx, exp); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Select the first node matching an XPath expression. * * @param contextNode The root node from which to evaluate the XPath * expression. * @param namespaceMapping A namespace prefix to namespace URI mapping * (String to String) for * evaluating the XPath expression. * @param exp The XPath expression to evaluate. * @return Node The first node matching the XPath expression, or * null, if no node matched. * @throws XPathException An error occurred evaluating the XPath expression. */ public static Node selectSingleNode(final Node contextNode, final Map namespaceMapping, final String exp) throws XPathException { final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping); return selectSingleNode(contextNode, ctx, exp); } /** * Select the first node matching an XPath expression. * * @param contextNode The root node from which to evaluate the XPath expression. * @param nsContext The NamespaceContext for resolving namespace * prefixes to namespace URIs for evaluating the XPath * expression. * @param exp The XPath expression to evaluate. * @return Node The first node matching the XPath expression, or * null, if no node matched. * @throws XPathException An error occurred evaluating the XPath expression. */ public static Node selectSingleNode(final Node contextNode, final NamespaceContext nsContext, final String exp) throws XPathException { try { final DOMXPath xpath = new DOMXPath(exp); xpath.setNamespaceContext(nsContext); return (Node) xpath.selectSingleNode(contextNode); } catch (final JaxenException e) { throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e); } } /** * Return the value of a DOM element whose location is given by an XPath * expression. * * @param root The root element from which to evaluate the XPath. * @param xpath The XPath expression pointing to the element whose value to * return. * @param def The default value to return, if no element can be found using * the given xpath. * @return The element value, if it can be located using the xpath. * Otherwise, def is returned. */ public static String getElementValue(final Element root, final String xpath, final String def) { final Element elem = (Element) XPathUtils.selectSingleNode(root, xpath); return elem != null ? DomUtils.getText(elem) : def; } /** * Return the value of a DOM attribute whose location is given by an XPath * expression. * * @param root The root element from which to evaluate the XPath. * @param xpath The XPath expression pointing to the attribute whose value to * return. * @param def The default value to return, if no attribute can be found using * the given xpath. * @return The element value, if it can be located using the xpath. * Otherwise, def is returned. */ public static String getAttributeValue(final Element root, final String xpath, final String def) { final Attr attr = (Attr) XPathUtils.selectSingleNode(root, xpath); return attr != null ? attr.getValue() : def; } /** * Returns the namespace prefix used within XPathUtils for * referring to the namespace of the specified (Security Layer command) element. *

* This namespace prefix can be used in various XPath expression evaluation * methods within XPathUtils without explicitely binding it to the * particular namespace. *

* * @param contextElement The (Security Layer command) element. * * @return the namespace prefix used within XPathUtils for * referring to the namespace of the specified (Security Layer command) * element. * * throws XpathException If the specified element has a namespace other * than the ones known by this implementation as valid Security Layer * namespaces (cf. * @link Constants#SL10_NS_URI, @link Constants#SL11_NS_URI, @link * Constants#SL12_NS_URI). */ public static String getSlPrefix(final Element contextElement) throws XPathException { final String sLNamespace = contextElement.getNamespaceURI(); String slPrefix = null; if (sLNamespace.equals(XmlNamespaceConstants.SL10_NS_URI)) { slPrefix = XmlNamespaceConstants.SL10_PREFIX; } else if (sLNamespace.equals(XmlNamespaceConstants.SL12_NS_URI)) { slPrefix = XmlNamespaceConstants.SL12_PREFIX; } else if (sLNamespace.equals(XmlNamespaceConstants.SL11_NS_URI)) { slPrefix = XmlNamespaceConstants.SL11_PREFIX; } else { throw new XPathException("XPath operation FAILED. Reason: "); } return slPrefix; } /** * Return the SecurityLayer namespace prefix of the context element. If the * context element is not the element that lies within the SecurityLayer * namespace. The Securitylayer namespace is derived from the * xmlns:sl10, sl11 or sl attribute of * the context element. *

* The returned prefix is needed for evaluating XPATH expressions. *

* * @param contextElement The element to get a prefix for the Securitylayer * namespace, that is used within the corresponding * document. * * @return The string sl10, sl11 or sl, * depending on the SecurityLayer namespace of the contextElement. * * throws XPathException If no (vlalid) SecurityLayer namespace prefix * or namespace is defined. */ public static String getSlPrefixFromNoRoot(final Element contextElement) throws XPathException { String slPrefix = checkSLnsDeclaration(contextElement, XmlNamespaceConstants.SL10_PREFIX, XmlNamespaceConstants.SL10_NS_URI); if (slPrefix == null) { slPrefix = checkSLnsDeclaration(contextElement, XmlNamespaceConstants.SL11_PREFIX, XmlNamespaceConstants.SL11_NS_URI); } if (slPrefix == null) { slPrefix = checkSLnsDeclaration(contextElement, XmlNamespaceConstants.SL12_PREFIX, XmlNamespaceConstants.SL12_NS_URI); } return slPrefix; } /** * Checks if the context element has an attribute xmlns:slPrefix * and if the prefix of that attribute corresponds with a valid SecurityLayer * namespace. * * @param contextElement The element to be checked. * @param slPrefix The prefix which should be checked. Must be a valid * SecurityLayer namespace prefix. * @param slNameSpace The SecurityLayer namespace that corresponds to the * specified prefix. * * @return The valid SecurityLayer prefix or null if this prefix is * not used. * @throws XPathException In case of an error */ private static String checkSLnsDeclaration(final Element contextElement, final String slPrefix, final String slNameSpace) throws XPathException { final String nsAtt = "xmlns:" + slPrefix; final String nameSpace = contextElement.getAttribute(nsAtt); if (nameSpace == "") { return null; } else { // check if namespace is correct if (nameSpace.equals(slNameSpace)) { return slPrefix; } else { throw new XPathException("Unknown Namespace declaration"); } } } }