1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright  1999-2004 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 package com.sun.org.apache.xml.internal.security.utils;
  22 
  23 import java.lang.ref.WeakReference;
  24 import java.util.Arrays;
  25 import java.util.WeakHashMap;
  26 import java.util.Map;
  27 
  28 import org.w3c.dom.Attr;
  29 import org.w3c.dom.Document;
  30 import org.w3c.dom.Element;
  31 import org.w3c.dom.NamedNodeMap;
  32 import org.w3c.dom.Node;
  33 
  34 
  35 /**
  36  * Purpose of this class is to enable the XML Parser to keep track of ID
  37  * attributes. This is done by 'registering' attributes of type ID at the
  38  * IdResolver. This is necessary if we create a document from scratch and we
  39  * sign some resources with a URI using a fragent identifier...
  40  * <BR />
  41  * The problem is that if you do not validate a document, you cannot use the
  42  * <CODE>getElementByID</CODE> functionality. So this modules uses some implicit
  43  * knowledge on selected Schemas and DTDs to pick the right Element for a given
  44  * ID: We know that all <CODE>@Id</CODE> attributes in an Element from the XML
  45  * Signature namespace are of type <CODE>ID</CODE>.
  46  *
  47  * @author $Author: mullan $
  48  * @see <A HREF="http://www.xml.com/lpt/a/2001/11/07/id.html">"Identity Crisis" on xml.com</A>
  49  */
  50 public class IdResolver {
  51 
  52     /** {@link java.util.logging} logging facility */
  53     private static java.util.logging.Logger log =
  54         java.util.logging.Logger.getLogger(IdResolver.class.getName());
  55 
  56     private static Map<Document, Map<String, WeakReference<Element>>> docMap =
  57                     new WeakHashMap<Document, Map<String, WeakReference<Element>>>();
  58 
  59     /**
  60      * Constructor IdResolver
  61      *
  62      */
  63     private IdResolver() {
  64        // we don't allow instantiation
  65     }
  66 
  67     /**
  68      * Method registerElementById
  69      *
  70      * @param element the element to register
  71      * @param idValue the value of the ID attribute
  72      */
  73     public static void registerElementById(Element element, String idValue) {
  74         Document doc = element.getOwnerDocument();
  75         Map<String, WeakReference<Element>> elementMap;
  76         synchronized (docMap) {
  77             elementMap = docMap.get(doc);
  78             if (elementMap == null) {
  79                 elementMap = new WeakHashMap<String, WeakReference<Element>>();
  80                 docMap.put(doc, elementMap);
  81             }
  82         }
  83         elementMap.put(idValue, new WeakReference<Element>(element));
  84     }
  85 
  86     /**
  87      * Method registerElementById
  88      *
  89      * @param element the element to register
  90      * @param id the ID attribute
  91      */
  92     public static void registerElementById(Element element, Attr id) {
  93         IdResolver.registerElementById(element, id.getNodeValue());
  94     }
  95 
  96     /**
  97      * Method getElementById
  98      *
  99      * @param doc the document
 100      * @param id the value of the ID
 101      * @return the element obtained by the id, or null if it is not found.
 102      */
 103     public static Element getElementById(Document doc, String id) {
 104 
 105         Element result = IdResolver.getElementByIdType(doc, id);
 106 
 107         if (result != null) {
 108             log.log(java.util.logging.Level.FINE,
 109             "I could find an Element using the simple getElementByIdType method: "
 110             + result.getTagName());
 111 
 112             return result;
 113         }
 114 
 115         result = IdResolver.getElementByIdUsingDOM(doc, id);
 116 
 117         if (result != null) {
 118             log.log(java.util.logging.Level.FINE,
 119              "I could find an Element using the simple getElementByIdUsingDOM method: "
 120             + result.getTagName());
 121 
 122             return result;
 123         }
 124         // this must be done so that Xalan can catch ALL namespaces
 125         //XMLUtils.circumventBug2650(doc);
 126         result = IdResolver.getElementBySearching(doc, id);
 127 
 128         if (result != null) {
 129             IdResolver.registerElementById(result, id);
 130 
 131             return result;
 132         }
 133 
 134         return null;
 135     }
 136 
 137 
 138     /**
 139      * Method getElementByIdUsingDOM
 140      *
 141      * @param doc the document
 142      * @param id the value of the ID
 143      * @return the element obtained by the id, or null if it is not found.
 144      */
 145     private static Element getElementByIdUsingDOM(Document doc, String id) {
 146         if (log.isLoggable(java.util.logging.Level.FINE))
 147             log.log(java.util.logging.Level.FINE, "getElementByIdUsingDOM() Search for ID " + id);
 148         return doc.getElementById(id);
 149     }
 150 
 151     /**
 152      * Method getElementByIdType
 153      *
 154      * @param doc the document
 155      * @param id the value of the ID
 156      * @return the element obtained by the id, or null if it is not found.
 157      */
 158     private static Element getElementByIdType(Document doc, String id) {
 159         if (log.isLoggable(java.util.logging.Level.FINE))
 160             log.log(java.util.logging.Level.FINE, "getElementByIdType() Search for ID " + id);
 161         Map<String, WeakReference<Element>> elementMap;
 162         synchronized (docMap) {
 163             elementMap = docMap.get(doc);
 164         }
 165         if (elementMap != null) {
 166             WeakReference<Element> weakReference =  elementMap.get(id);
 167             if (weakReference != null) {
 168                 return weakReference.get();
 169             }
 170         }
 171         return null;
 172     }
 173 
 174     private static java.util.List<String> names;
 175     private static int namesLength;
 176     static {
 177         String namespaces[]={
 178             Constants.SignatureSpecNS,
 179             EncryptionConstants.EncryptionSpecNS,
 180             "http://schemas.xmlsoap.org/soap/security/2000-12",
 181             "http://www.w3.org/2002/03/xkms#",
 182             "urn:oasis:names:tc:SAML:1.0:assertion",
 183             "urn:oasis:names:tc:SAML:1.0:protocol"
 184         };
 185         names = Arrays.asList(namespaces);
 186         namesLength = names.size();
 187     }
 188 
 189 
 190     private static Element getElementBySearching(Node root,String id) {
 191         Element []els=new Element[namesLength + 1];
 192         getEl(root,id,els);
 193         for (int i=0;i<els.length;i++) {
 194             if (els[i]!=null) {
 195                 return els[i];
 196             }
 197         }
 198         return null;
 199     }
 200 
 201     private static int getEl(Node currentNode,String id,Element []els) {
 202         Node sibling=null;
 203         Node parentNode=null;
 204         do {
 205                 switch (currentNode.getNodeType()) {
 206                 case Node.DOCUMENT_FRAGMENT_NODE :
 207                 case Node.DOCUMENT_NODE :
 208                         sibling= currentNode.getFirstChild();
 209                         break;
 210 
 211 
 212                 case Node.ELEMENT_NODE :
 213                         Element currentElement = (Element) currentNode;
 214                         if (isElement(currentElement, id, els)==1)
 215                                 return 1;
 216                         sibling= currentNode.getFirstChild();
 217                         if (sibling==null) {
 218                             if (parentNode != null) {
 219                                         sibling= currentNode.getNextSibling();
 220                                     }
 221                         } else {
 222                                 parentNode=currentElement;
 223                         }
 224                         break;
 225         } while (sibling==null  && parentNode!=null) {
 226                         sibling=parentNode.getNextSibling();
 227                         parentNode=parentNode.getParentNode();
 228                         if (parentNode != null && parentNode.getNodeType() != Node.ELEMENT_NODE) {
 229                                 parentNode=null;
 230                         }
 231                 }
 232                 if (sibling==null)
 233                         return 1;
 234                 currentNode=sibling;
 235                 sibling=currentNode.getNextSibling();
 236         } while(true);
 237 
 238     }
 239     public static int isElement(Element el, String id,Element[] els) {
 240         if (!el.hasAttributes()) {
 241                 return 0;
 242         }
 243         NamedNodeMap ns=el.getAttributes();
 244         int elementIndex=names.indexOf(el.getNamespaceURI());
 245             elementIndex=(elementIndex<0) ? namesLength : elementIndex;
 246         for (int length=ns.getLength(), i=0; i<length; i++) {
 247                 Attr n=(Attr)ns.item(i);
 248                 String s=n.getNamespaceURI();
 249 
 250                     int index=s==null ? elementIndex : names.indexOf(n.getNamespaceURI());
 251                     index=(index<0) ? namesLength : index;
 252                     String name=n.getLocalName();
 253                     if (name == null)
 254                         name = n.getName();
 255                     if (name.length()>2)
 256                         continue;
 257                     String value=n.getNodeValue();
 258                     if (name.charAt(0)=='I') {
 259                         char ch=name.charAt(1);
 260                         if (ch=='d' && value.equals(id)) {
 261                                 els[index]=el;
 262                                 if (index==0) {
 263                                         return 1;
 264                                 }
 265                         } else if (ch=='D' &&value.endsWith(id)) {
 266                                 if (index!=3) {
 267                                     index=namesLength;
 268                                 }
 269                                 els[index]=el;
 270                         }
 271                     } else if ( "id".equals(name) && value.equals(id) ) {
 272                         if (index!=2) {
 273                                 index=namesLength;
 274                         }
 275                         els[index]=el;
 276                     }
 277         }
 278         //For an element namespace search for importants
 279         if ((elementIndex==3)&&(
 280                     el.getAttribute("OriginalRequestID").equals(id) ||
 281                     el.getAttribute("RequestID").equals(id) ||
 282                     el.getAttribute("ResponseID").equals(id))) {
 283                     els[3]=el;
 284         } else if ((elementIndex==4)&&(
 285                     el.getAttribute("AssertionID").equals(id))) {
 286                     els[4]=el;
 287         } else if ((elementIndex==5)&&(
 288                     el.getAttribute("RequestID").equals(id) ||
 289                     el.getAttribute("ResponseID").equals(id))) {
 290                     els[5]=el;
 291                  }
 292         return 0;
 293     }
 294 }