
/*
 * Copyright  1999-2004 The Apache Software Foundation.
 *
 *  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.apache.xml.security.c14n.implementations;



import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.helper.C14nHelper;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;


/**
 * Implements <A HREF="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">Canonical
 * XML Version 1.0</A>, a W3C Recommendation from 15 March 2001.
 *
 * @author Christian Geuer-Pollmann <geuerp@apache.org>
 * @version $Revision: 570503 $
 */
public abstract class Canonicalizer20010315 extends CanonicalizerBase {
	boolean firstCall=true;
	final SortedSet result= new TreeSet(COMPARE);
    static final String XMLNS_URI=Constants.NamespaceSpecNS;
    static final String XML_LANG_URI=Constants.XML_LANG_SPACE_SpecNS;
    static class XmlAttrStack {
    	int currentLevel=0;
    	int lastlevel=0;
    	XmlsStackElement cur;
    	static class XmlsStackElement {
    		int level;
    		boolean rendered=false;
    		List nodes=new ArrayList();
    	};
    	List levels=new ArrayList();    	
    	void push(int level) {
    		currentLevel=level;
    		if (currentLevel==-1)
    			return;
    		cur=null;
    		while (lastlevel>=currentLevel) {
    			levels.remove(levels.size()-1);
    			if (levels.size()==0) {
    				lastlevel=0;
    				return;    				
    			}
    			lastlevel=((XmlsStackElement)levels.get(levels.size()-1)).level;
    		}
    	}
    	void addXmlnsAttr(Attr n) {
    		if (cur==null) {
    			cur=new XmlsStackElement();
    			cur.level=currentLevel;
    			levels.add(cur);
    			lastlevel=currentLevel;
    		}
    		cur.nodes.add(n);
    	}
    	void getXmlnsAttr(Collection col) {
    		int size=levels.size()-1;
    		if (cur==null) {
    			cur=new XmlsStackElement();
    			cur.level=currentLevel;
    			lastlevel=currentLevel;
    			levels.add(cur);
    		}
    		boolean parentRendered=false;
    		XmlsStackElement e=null;
    		if (size==-1) {
    			parentRendered=true;
    		} else {
    			e=(XmlsStackElement)levels.get(size);
    			if (e.rendered && e.level+1==currentLevel)
    				parentRendered=true;
        		
    		}
    		if (parentRendered) {
				col.addAll(cur.nodes);
				cur.rendered=true;
				return;
			}
    		
			Map loa = new HashMap();    		
    		for (;size>=0;size--) {
    			e=(XmlsStackElement)levels.get(size);
    			Iterator it=e.nodes.iterator();
    			while (it.hasNext()) {
    				Attr n=(Attr)it.next();
    				if (!loa.containsKey(n.getName()))
    					loa.put(n.getName(),n);
    			}
    			//if (e.rendered)
    				//break;
    			
    		};
    		//cur.nodes.clear();
    		//cur.nodes.addAll(loa.values());
			cur.rendered=true;
    		col.addAll(loa.values());
    	}
    	
    }
    XmlAttrStack xmlattrStack=new XmlAttrStack();
    /**
    * Constructor Canonicalizer20010315
    *
    * @param includeComments
    */
   public Canonicalizer20010315(boolean includeComments) {
      super(includeComments);
   }

   /**
    * Returns the Attr[]s to be outputted for the given element.
    * <br>
    * The code of this method is a copy of {@link #handleAttributes(Element,
    * NameSpaceSymbTable)},
    * whereas it takes into account that subtree-c14n is -- well -- subtree-based.
    * So if the element in question isRoot of c14n, it's parent is not in the
    * node set, as well as all other ancestors.
    *
    * @param E
    * @param ns
    * @return the Attr[]s to be outputted
    * @throws CanonicalizationException
    */
   Iterator handleAttributesSubtree(Element E,  NameSpaceSymbTable ns )
           throws CanonicalizationException {
   	  if (!E.hasAttributes() && !firstCall) {
         return null; 
      }
      // result will contain the attrs which have to be outputted   	  
      final SortedSet result = this.result;       
      result.clear();
      NamedNodeMap attrs = E.getAttributes();
      int attrsLength = attrs.getLength();      
            
      for (int i = 0; i < attrsLength; i++) {
         Attr N = (Attr) attrs.item(i);
         String NUri =N.getNamespaceURI();

         if (XMLNS_URI!=NUri) {
         	//It's not a namespace attr node. Add to the result and continue.
            result.add(N);
            continue;
         }

         String NName=N.getLocalName();
         String NValue=N.getValue();        
         if (XML.equals(NName)
                 && XML_LANG_URI.equals(NValue)) {
         	//The default mapping for xml must not be output.
         	continue;
         }
         
         Node n=ns.addMappingAndRender(NName,NValue,N);          		 
		 	 
      	  if (n!=null) {
      	  	 //Render the ns definition
             result.add(n);
             if (C14nHelper.namespaceIsRelative(N)) {
                Object exArgs[] = { E.getTagName(), NName, N.getNodeValue() };
                throw new CanonicalizationException(
                   "c14n.Canonicalizer.RelativeNamespace", exArgs);
             }
          }        
      }
            	   
      if (firstCall) {
      	//It is the first node of the subtree
      	//Obtain all the namespaces defined in the parents, and added to the output.
      	ns.getUnrenderedNodes(result);          	      		            
      	//output the attributes in the xml namespace.
      	xmlattrStack.getXmlnsAttr(result);
		firstCall=false;
      } 
      
      return result.iterator();
   }

   /**
    * Returns the Attr[]s to be outputted for the given element.
    * <br>
    * IMPORTANT: This method expects to work on a modified DOM tree, i.e. a DOM which has
    * been prepared using {@link org.apache.xml.security.utils.XMLUtils#circumventBug2650(
    * org.w3c.dom.Document)}.
    * 
    * @param E
    * @param ns
    * @return the Attr[]s to be outputted
    * @throws CanonicalizationException
    */
   Iterator handleAttributes(Element E,  NameSpaceSymbTable ns ) throws CanonicalizationException {    
    // result will contain the attrs which have to be outputted
	xmlattrStack.push(ns.getLevel());
    boolean isRealVisible=isVisibleDO(E,ns.getLevel())==1;    
    NamedNodeMap attrs = null;
    int attrsLength = 0;
    if (E.hasAttributes()) {
        attrs=E.getAttributes();
       attrsLength= attrs.getLength();
    }
    
    
    SortedSet result = this.result;       
    result.clear();
            
    for (int i = 0; i < attrsLength; i++) {
       Attr N = (Attr) attrs.item(i);
       String NUri =N.getNamespaceURI();
       
       if (XMLNS_URI!=NUri) {
       	  //A non namespace definition node.
    	   if (XML_LANG_URI==NUri) {
        		  xmlattrStack.addXmlnsAttr(N);
           } else if (isRealVisible){
       		//The node is visible add the attribute to the list of output attributes.
           	result.add(N);
          } 
       	  //keep working
          continue;
       }

       String NName=N.getLocalName();
       String NValue=N.getValue();              
       if ("xml".equals(NName)
               && XML_LANG_URI.equals(NValue)) {
          /* except omit namespace node with local name xml, which defines
           * the xml prefix, if its string value is http://www.w3.org/XML/1998/namespace.
           */
          continue;
       }
       //add the prefix binding to the ns symb table.
       //ns.addInclusiveMapping(NName,NValue,N,isRealVisible);          
	    if  (isVisible(N))  {
	    	if (!isRealVisible && ns.removeMappingIfRender(NName)) {
	    		continue;
	    	} 
			//The xpath select this node output it if needed.
	    	//Node n=ns.addMappingAndRenderXNodeSet(NName,NValue,N,isRealVisible);
	    	Node n=ns.addMappingAndRender(NName,NValue,N);
		 	if (n!=null) {
		 	 		result.add(n);
                    if (C14nHelper.namespaceIsRelative(N)) {
                       Object exArgs[] = { E.getTagName(), NName, N.getNodeValue() };
                       throw new CanonicalizationException(
                          "c14n.Canonicalizer.RelativeNamespace", exArgs);
                   }
		 	 }
    	} else {
    		if (isRealVisible && NName!=XMLNS) {
    			ns.removeMapping(NName);	
    		} else {
    			ns.addMapping(NName,NValue,N);
    		}
    	}
    }
    if (isRealVisible) {    	           
    	//The element is visible, handle the xmlns definition        
        Attr xmlns = E.getAttributeNodeNS(XMLNS_URI, XMLNS);
        Node n=null;
        if (xmlns == null) {
        	//No xmlns def just get the already defined.
        	n=ns.getMapping(XMLNS);        		
        } else if ( !isVisible(xmlns)) {
        	//There is a definition but the xmlns is not selected by the xpath.
        	//then xmlns=""
        	n=ns.addMappingAndRender(XMLNS,"",nullNode);        	    		      	
        }
        //output the xmlns def if needed.
        if (n!=null) {
    			result.add(n);
    	}
        //Float all xml:* attributes of the unselected parent elements to this one. 
    	//addXmlAttributes(E,result);
        xmlattrStack.getXmlnsAttr(result);
    	ns.getUnrenderedNodes(result);
        
    }
    
    return result.iterator();
   }
   /**
    * Always throws a CanonicalizationException because this is inclusive c14n.
    *
    * @param xpathNodeSet
    * @param inclusiveNamespaces
    * @return none it always fails
    * @throws CanonicalizationException always
    */
   public byte[] engineCanonicalizeXPathNodeSet(Set xpathNodeSet, String inclusiveNamespaces)
           throws CanonicalizationException {

      /** $todo$ well, should we throw UnsupportedOperationException ? */
      throw new CanonicalizationException(
         "c14n.Canonicalizer.UnsupportedOperation");
   }

   /**
    * Always throws a CanonicalizationException because this is inclusive c14n.
    *
    * @param rootNode
    * @param inclusiveNamespaces
    * @return none it always fails
    * @throws CanonicalizationException
    */
   public byte[] engineCanonicalizeSubTree(Node rootNode, String inclusiveNamespaces)
           throws CanonicalizationException {

      /** $todo$ well, should we throw UnsupportedOperationException ? */
      throw new CanonicalizationException(
         "c14n.Canonicalizer.UnsupportedOperation");
   }
   void circumventBugIfNeeded(XMLSignatureInput input) throws CanonicalizationException, ParserConfigurationException, IOException, SAXException {
	   if (!input.isNeedsToBeExpanded()) 
		   return;
	   Document doc = null;
       if (input.getSubNode() != null) {
           doc=XMLUtils.getOwnerDocument(input.getSubNode());
       } else {
           doc=XMLUtils.getOwnerDocument(input.getNodeSet());
       }
	   XMLUtils.circumventBug2650(doc);
		
   }
   
   void handleParent(Element e, NameSpaceSymbTable ns) {
	   if (!e.hasAttributes()) {
			return;
	   }
	   xmlattrStack.push(-1);
	   NamedNodeMap attrs = e.getAttributes();
	   int attrsLength = attrs.getLength();
	   for (int i = 0; i < attrsLength; i++) {
		   Attr N = (Attr) attrs.item(i);
		   if (Constants.NamespaceSpecNS!=N.getNamespaceURI()) {			   
			   //Not a namespace definition, ignore.
			   if (XML_LANG_URI==N.getNamespaceURI()) {
				   xmlattrStack.addXmlnsAttr(N);
			   }
			   continue;
		   }

		   String NName=N.getLocalName();
		   String NValue=N.getNodeValue();
		   if (XML.equals(NName)
				   && Constants.XML_LANG_SPACE_SpecNS.equals(NValue)) {
				continue;
		   }            
		   ns.addMapping(NName,NValue,N);             
	   }
   }
}
