1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 2005 The Apache Software Foundation.
   7  *
   8  *  Licensed under the Apache License, Version 2.0 (the "License");
   9  *  you may not use this file except in compliance with the License.
  10  *  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  *  Unless required by applicable law or agreed to in writing, software
  15  *  distributed under the License is distributed on an "AS IS" BASIS,
  16  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  *  See the License for the specific language governing permissions and
  18  *  limitations under the License.
  19  *
  20  */
  21 /*
  22  * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
  23  */
  24 /*
  25  * $Id: DOMUtils.java,v 1.2 2008/07/24 15:20:32 mullan Exp $
  26  */
  27 package org.jcp.xml.dsig.internal.dom;
  28 
  29 import java.util.*;
  30 import java.security.spec.AlgorithmParameterSpec;
  31 import org.w3c.dom.Attr;
  32 import org.w3c.dom.Document;
  33 import org.w3c.dom.Element;
  34 import org.w3c.dom.Node;
  35 import org.w3c.dom.NodeList;
  36 import javax.xml.crypto.*;
  37 import javax.xml.crypto.dsig.dom.DOMSignContext;
  38 import javax.xml.crypto.dsig.*;
  39 import javax.xml.crypto.dsig.spec.*;
  40 
  41 /**
  42  * Useful static DOM utility methods.
  43  *
  44  * @author Sean Mullan
  45  */
  46 public class DOMUtils {
  47 
  48     // class cannot be instantiated
  49     private DOMUtils() {}
  50 
  51     /**
  52      * Returns the owner document of the specified node.
  53      *
  54      * @param node the node
  55      * @return the owner document
  56      */
  57     public static Document getOwnerDocument(Node node) {
  58         if (node.getNodeType() == Node.DOCUMENT_NODE) {
  59             return (Document) node;
  60         } else {
  61             return node.getOwnerDocument();
  62         }
  63     }
  64 
  65     /**
  66      * Creates an element in the specified namespace, with the specified tag
  67      * and namespace prefix.
  68      *
  69      * @param doc the owner document
  70      * @param tag the tag
  71      * @param nsURI the namespace URI
  72      * @param prefix the namespace prefix
  73      * @return the newly created element
  74      */
  75     public static Element createElement(Document doc, String tag, String nsURI,
  76         String prefix) {
  77         String qName = (prefix == null || prefix.length() == 0)
  78                        ? tag : prefix + ":" + tag;
  79         return doc.createElementNS(nsURI, qName);
  80     }
  81 
  82     /**
  83      * Sets an element's attribute (using DOM level 2) with the
  84      * specified value and namespace prefix.
  85      *
  86      * @param elem the element to set the attribute on
  87      * @param name the name of the attribute
  88      * @param value the attribute value. If null, no attribute is set.
  89      */
  90     public static void setAttribute(Element elem, String name, String value) {
  91         if (value == null) return;
  92         elem.setAttributeNS(null, name, value);
  93     }
  94 
  95     /**
  96      * Sets an element's attribute (using DOM level 2) with the
  97      * specified value and namespace prefix AND registers the ID value with
  98      * the specified element. This is for resolving same-document
  99      * ID references.
 100      *
 101      * @param elem the element to set the attribute on
 102      * @param name the name of the attribute
 103      * @param value the attribute value. If null, no attribute is set.
 104      */
 105     public static void setAttributeID(Element elem, String name, String value) {
 106         if (value == null) return;
 107         elem.setAttributeNS(null, name, value);
 108         elem.setIdAttributeNS(null, name, true);
 109     }
 110 
 111     /**
 112      * Returns the first child element of the specified node, or null if there
 113      * is no such element.
 114      *
 115      * @param node the node
 116      * @return the first child element of the specified node, or null if there
 117      *    is no such element
 118      * @throws NullPointerException if <code>node == null</code>
 119      */
 120     public static Element getFirstChildElement(Node node) {
 121         Node child = node.getFirstChild();
 122         while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
 123             child = child.getNextSibling();
 124         }
 125         return (Element) child;
 126     }
 127 
 128     /**
 129      * Returns the last child element of the specified node, or null if there
 130      * is no such element.
 131      *
 132      * @param node the node
 133      * @return the last child element of the specified node, or null if there
 134      *    is no such element
 135      * @throws NullPointerException if <code>node == null</code>
 136      */
 137     public static Element getLastChildElement(Node node) {
 138         Node child = node.getLastChild();
 139         while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
 140             child = child.getPreviousSibling();
 141         }
 142         return (Element) child;
 143     }
 144 
 145     /**
 146      * Returns the next sibling element of the specified node, or null if there
 147      * is no such element.
 148      *
 149      * @param node the node
 150      * @return the next sibling element of the specified node, or null if there
 151      *    is no such element
 152      * @throws NullPointerException if <code>node == null</code>
 153      */
 154     public static Element getNextSiblingElement(Node node) {
 155         Node sibling = node.getNextSibling();
 156         while (sibling != null && sibling.getNodeType() != Node.ELEMENT_NODE) {
 157             sibling = sibling.getNextSibling();
 158         }
 159         return (Element) sibling;
 160     }
 161 
 162     /**
 163      * Returns the attribute value for the attribute with the specified name.
 164      * Returns null if there is no such attribute, or
 165      * the empty string if the attribute value is empty.
 166      *
 167      * <p>This works around a limitation of the DOM
 168      * <code>Element.getAttributeNode</code> method, which does not distinguish
 169      * between an unspecified attribute and an attribute with a value of
 170      * "" (it returns "" for both cases).
 171      *
 172      * @param elem the element containing the attribute
 173      * @param name the name of the attribute
 174      * @return the attribute value (may be null if unspecified)
 175      */
 176     public static String getAttributeValue(Element elem, String name) {
 177         Attr attr = elem.getAttributeNodeNS(null, name);
 178         return (attr == null) ? null : attr.getValue();
 179     }
 180 
 181     /**
 182      * Returns a Set of <code>Node</code>s, backed by the specified
 183      * <code>NodeList</code>.
 184      *
 185      * @param nl the NodeList
 186      * @return a Set of Nodes
 187      */
 188     public static Set nodeSet(NodeList nl) {
 189         return new NodeSet(nl);
 190     }
 191 
 192     static class NodeSet extends AbstractSet {
 193         private NodeList nl;
 194         public NodeSet(NodeList nl) {
 195             this.nl = nl;
 196         }
 197 
 198         public int size() { return nl.getLength(); }
 199         public Iterator iterator() {
 200             return new Iterator() {
 201                 int index = 0;
 202 
 203                 public void remove() {
 204                     throw new UnsupportedOperationException();
 205                 }
 206                 public Object next() {
 207                     if (!hasNext()) {
 208                         throw new NoSuchElementException();
 209                     }
 210                     return nl.item(index++);
 211                 }
 212                 public boolean hasNext() {
 213                     return index < nl.getLength() ? true : false;
 214                 }
 215             };
 216         }
 217     }
 218 
 219     /**
 220      * Returns the prefix associated with the specified namespace URI
 221      *
 222      * @param context contains the namespace map
 223      * @param nsURI the namespace URI
 224      * @return the prefix associated with the specified namespace URI, or
 225      *    null if not set
 226      */
 227     public static String getNSPrefix(XMLCryptoContext context, String nsURI) {
 228         if (context != null) {
 229             return context.getNamespacePrefix
 230                 (nsURI, context.getDefaultNamespacePrefix());
 231         } else {
 232             return null;
 233         }
 234     }
 235 
 236     /**
 237      * Returns the prefix associated with the XML Signature namespace URI
 238      *
 239      * @param context contains the namespace map
 240      * @return the prefix associated with the specified namespace URI, or
 241      *    null if not set
 242      */
 243     public static String getSignaturePrefix(XMLCryptoContext context) {
 244         return getNSPrefix(context, XMLSignature.XMLNS);
 245     }
 246 
 247     /**
 248      * Removes all children nodes from the specified node.
 249      *
 250      * @param node the parent node whose children are to be removed
 251      */
 252     public static void removeAllChildren(Node node) {
 253         NodeList children = node.getChildNodes();
 254         for (int i = 0, length = children.getLength(); i < length; i++) {
 255             node.removeChild(children.item(i));
 256         }
 257     }
 258 
 259     /**
 260      * Compares 2 nodes for equality. Implementation is not complete.
 261      */
 262     public static boolean nodesEqual(Node thisNode, Node otherNode) {
 263         if (thisNode == otherNode) {
 264             return true;
 265         }
 266         if (thisNode.getNodeType() != otherNode.getNodeType()) {
 267             return false;
 268         }
 269         // FIXME - test content, etc
 270         return true;
 271     }
 272 
 273     /**
 274      * Checks if child element has same owner document before
 275      * appending to the parent, and imports it to the parent's document
 276      * if necessary.
 277      */
 278     public static void appendChild(Node parent, Node child) {
 279         Document ownerDoc = getOwnerDocument(parent);
 280         if (child.getOwnerDocument() != ownerDoc) {
 281             parent.appendChild(ownerDoc.importNode(child, true));
 282         } else {
 283             parent.appendChild(child);
 284         }
 285     }
 286 
 287     public static boolean paramsEqual(AlgorithmParameterSpec spec1,
 288         AlgorithmParameterSpec spec2) {
 289         if (spec1 == spec2) {
 290             return true;
 291         }
 292         if (spec1 instanceof XPathFilter2ParameterSpec &&
 293             spec2 instanceof XPathFilter2ParameterSpec) {
 294             return paramsEqual((XPathFilter2ParameterSpec) spec1,
 295                 (XPathFilter2ParameterSpec) spec2);
 296         }
 297         if (spec1 instanceof ExcC14NParameterSpec &&
 298             spec2 instanceof ExcC14NParameterSpec) {
 299             return paramsEqual((ExcC14NParameterSpec) spec1,
 300                 (ExcC14NParameterSpec) spec2);
 301         }
 302         if (spec1 instanceof XPathFilterParameterSpec &&
 303             spec2 instanceof XPathFilterParameterSpec) {
 304             return paramsEqual((XPathFilterParameterSpec) spec1,
 305                 (XPathFilterParameterSpec) spec2);
 306         }
 307         if (spec1 instanceof XSLTTransformParameterSpec &&
 308             spec2 instanceof XSLTTransformParameterSpec) {
 309             return paramsEqual((XSLTTransformParameterSpec) spec1,
 310                 (XSLTTransformParameterSpec) spec2);
 311         }
 312         return false;
 313     }
 314 
 315     private static boolean paramsEqual(XPathFilter2ParameterSpec spec1,
 316         XPathFilter2ParameterSpec spec2) {
 317 
 318         List types = spec1.getXPathList();
 319         List otypes = spec2.getXPathList();
 320         int size = types.size();
 321         if (size != otypes.size()) {
 322             return false;
 323         }
 324         for (int i = 0; i < size; i++) {
 325             XPathType type = (XPathType) types.get(i);
 326             XPathType otype = (XPathType) otypes.get(i);
 327             if (!type.getExpression().equals(otype.getExpression()) ||
 328                 !type.getNamespaceMap().equals(otype.getNamespaceMap()) ||
 329                 type.getFilter() != otype.getFilter()) {
 330                 return false;
 331             }
 332         }
 333         return true;
 334     }
 335 
 336     private static boolean paramsEqual(ExcC14NParameterSpec spec1,
 337         ExcC14NParameterSpec spec2) {
 338         return spec1.getPrefixList().equals(spec2.getPrefixList());
 339     }
 340 
 341     private static boolean paramsEqual(XPathFilterParameterSpec spec1,
 342         XPathFilterParameterSpec spec2) {
 343         return (spec1.getXPath().equals(spec2.getXPath()) &&
 344             spec1.getNamespaceMap().equals(spec2.getNamespaceMap()));
 345     }
 346 
 347     private static boolean paramsEqual(XSLTTransformParameterSpec spec1,
 348         XSLTTransformParameterSpec spec2) {
 349 
 350         XMLStructure ostylesheet = spec2.getStylesheet();
 351         if (!(ostylesheet instanceof javax.xml.crypto.dom.DOMStructure)) {
 352             return false;
 353         }
 354         Node ostylesheetElem =
 355             ((javax.xml.crypto.dom.DOMStructure) ostylesheet).getNode();
 356         XMLStructure stylesheet = spec1.getStylesheet();
 357         Node stylesheetElem =
 358             ((javax.xml.crypto.dom.DOMStructure) stylesheet).getNode();
 359         return nodesEqual(stylesheetElem, ostylesheetElem);
 360     }
 361 }