2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / System.Security / System.Security.Cryptography.Xml / XmlDsigXPathTransform.cs
index b55f0e078a0a0851ad9c25618bcd392660ab3962..1b7c9b3932384ea6a78dec03b277c8e96a745ca2 100644 (file)
@@ -4,40 +4,69 @@
 // http://www.w3.org/TR/1999/REC-xpath-19991116 
 //
 // Author:
-//     Sebastien Pouliot (spouliot@motus.com)
+//     Sebastien Pouliot <sebastien@ximian.com>
+//     Atsushi Enomoto <atsushi@ximian.com>
 //
 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
 //
 
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Collections;
 using System.IO;
 using System.Text;
 using System.Xml;
+using System.Xml.XPath;
+using System.Xml.Xsl;
 
-namespace System.Security.Cryptography.Xml {
+namespace System.Security.Cryptography.Xml 
+{
 
        // www.w3.org/TR/xmldsig-core/
        // see Section 6.6.3 of the XMLDSIG specification
-       [MonoTODO]
-       public class XmlDsigXPathTransform : Transform {
+       public class XmlDsigXPathTransform : Transform 
+       {
 
-               private Type[] input;
-               private Type[] output;
-               private XmlNodeList xnl;
-               private XmlNodeList xpathNodes;
+               private Type [] input;
+               private Type [] output;
+               private XmlNodeList xpath;
+               private XmlDocument doc;
+               private XsltContext ctx;
 
                public XmlDsigXPathTransform () 
                {
+                       Algorithm = "http://www.w3.org/TR/1999/REC-xpath-19991116";
                }
 
-               public override Type[] InputTypes {
+               public override Type [] InputTypes {
                        get {
                                if (input == null) {
                                        lock (this) {
                                                // this way the result is cached if called multiple time
                                                input = new Type [3];
-                                               input[0] = typeof (System.IO.Stream);
-                                               input[1] = typeof (System.Xml.XmlDocument);
-                                               input[2] = typeof (System.Xml.XmlNodeList);
+                                               input [0] = typeof (System.IO.Stream);
+                                               input [1] = typeof (System.Xml.XmlDocument);
+                                               input [2] = typeof (System.Xml.XmlNodeList);
                                        }
                                }
                                return input;
@@ -50,7 +79,7 @@ namespace System.Security.Cryptography.Xml {
                                        lock (this) {
                                                // this way the result is cached if called multiple time
                                                output = new Type [1];
-                                               output[0] = typeof (System.IO.Stream);
+                                               output [0] = typeof (System.Xml.XmlNodeList);
                                        }
                                }
                                return output;
@@ -59,12 +88,42 @@ namespace System.Security.Cryptography.Xml {
 
                protected override XmlNodeList GetInnerXml () 
                {
-                       return xnl;
+                       if (xpath == null) {
+                               // default value
+                               XmlDocument xpdoc = new XmlDocument ();
+                               xpdoc.LoadXml ("<XPath xmlns=\"" + XmlSignature.NamespaceURI + "\"></XPath>");
+                               xpath = xpdoc.ChildNodes;
+                       }
+                       return xpath;
                }
 
+               [MonoTODO ("Evaluation of extension function here() results in different from MS.NET (is MS.NET really correct??).")]
                public override object GetOutput () 
                {
-                       return xpathNodes;
+                       if (xpath == null)
+                               return new XmlDsigNodeList (new ArrayList ());
+
+                       // evaluate every time since input or xpath might have changed.
+                       string x = null;
+                       for (int i = 0; i < xpath.Count; i++) {
+                               switch (xpath [i].NodeType) {
+                               case XmlNodeType.Text:
+                               case XmlNodeType.CDATA:
+                               case XmlNodeType.Element:
+                                       x += xpath [i].InnerText;
+                                       break;
+                               }
+                       }
+
+                       ctx = new XmlDsigXPathContext (doc);
+                       foreach (XmlNode n in xpath) {
+                               XPathNavigator nav = n.CreateNavigator ();
+                               XPathNodeIterator iter = nav.Select ("namespace::*");
+                               while (iter.MoveNext ())
+                                       if (iter.Current.LocalName != "xml")
+                                               ctx.AddNamespace (iter.Current.LocalName, iter.Current.Value);
+                       }
+                       return EvaluateMatch (doc, x);
                }
 
                public override object GetOutput (Type type) 
@@ -74,34 +133,166 @@ namespace System.Security.Cryptography.Xml {
                        return GetOutput ();
                }
 
+               private XmlDsigNodeList EvaluateMatch (XmlNode n, string xpath)
+               {
+                       ArrayList al = new ArrayList ();
+                       // Strictly to say, document node is explicitly
+                       // excluded by W3C spec (context node is initialized
+                       // to the document root and XPath expression is
+                       // "//. | //@* | //namespace::*)
+                       XPathNavigator nav = n.CreateNavigator ();
+                       XPathExpression exp = nav.Compile (xpath);
+                       exp.SetContext (ctx);
+                       EvaluateMatch (n, exp, al);
+                       return new XmlDsigNodeList (al);
+               }
+
+               private void EvaluateMatch (XmlNode n, XPathExpression exp, ArrayList al)
+               {
+                       if (NodeMatches (n, exp))
+                               al.Add (n);
+                       if (n.Attributes != null)
+                               for (int i = 0; i < n.Attributes.Count; i++)
+                                       if (NodeMatches (n.Attributes [i], exp))
+                                               al.Add (n.Attributes [i]);
+                       for (int i = 0; i < n.ChildNodes.Count; i++)
+                               EvaluateMatch (n.ChildNodes [i], exp, al);
+               }
+
+               private bool NodeMatches (XmlNode n, XPathExpression exp)
+               {
+                       // This looks waste of memory since it creates 
+                       // XPathNavigator every time, but even if we use
+                       //  XPathNodeIterator.Current, it also clones every time.
+                       object ret = n.CreateNavigator ().Evaluate (exp);
+                       if (ret is bool)
+                               return (bool) ret;
+                       if (ret is double) {
+                               double d = (double) ret;
+                               return !(d == 0.0 || d == double.NaN);
+                       }
+                       if (ret is string)
+                               return ((string) ret).Length > 0;
+                       if (ret is XPathNodeIterator) {
+                               XPathNodeIterator retiter = (XPathNodeIterator) ret;
+                               return retiter.Count > 0;
+                       }
+                       return false;
+               }
+
                public override void LoadInnerXml (XmlNodeList nodeList) 
                {
                        if (nodeList == null)
                                throw new CryptographicException ("nodeList");
-                       xnl = nodeList;
+                       xpath = nodeList;
                }
 
                public override void LoadInput (object obj) 
                {
-                       XmlNode xn = null;
                        // possible input: Stream, XmlDocument, and XmlNodeList
                        if (obj is Stream) {
-                               XmlDocument doc = new XmlDocument ();
+                               doc = new XmlDocument ();
+#if ! NET_1_0
+                               doc.XmlResolver = GetResolver ();
+#endif
                                doc.Load (obj as Stream);
                        }
                        else if (obj is XmlDocument) {
+                               doc = (obj as XmlDocument);
                        }
                        else if (obj is XmlNodeList) {
-                               xnl = (XmlNodeList) obj;
+                               doc = new XmlDocument ();
+#if ! NET_1_0
+                               doc.XmlResolver = GetResolver ();
+#endif
+                               foreach (XmlNode xn in (obj as XmlNodeList))  {
+                                       XmlNode importedNode = doc.ImportNode (xn, true);
+                                       doc.AppendChild (importedNode);
+                               }
                        }
+               }
+
+               // Internal classes to support XPath extension function here()
+
+               internal class XmlDsigXPathContext : XsltContext
+               {
+                       XmlDsigXPathFunctionHere here;
+                       public XmlDsigXPathContext (XmlNode node)
+                       {
+                               here = new XmlDsigXPathFunctionHere (node);
+                       }
+
+                       public override IXsltContextFunction ResolveFunction (
+                               string prefix, string name, XPathResultType [] argType)
+                       {
+                               // Here MS.NET incorrectly allows arbitrary
+                               // name e.g. "heretic()".
+                               if (name == "here" &&
+                                       prefix == String.Empty &&
+                                       argType.Length == 0)
+                                       return here;
+                               else
+                                       return null; // ????
+                       }
+
+                       public override bool Whitespace {
+                               get { return true; }
+                       }
+
+                       public override bool PreserveWhitespace (XPathNavigator node)
+                       {
+                               return true;
+                       }
+
+                       public override int CompareDocument (string s1, string s2)
+                       {
+                               return String.Compare (s1, s2);
+                       }
+
+                       public override IXsltContextVariable ResolveVariable (string prefix, string name)
+                       {
+                               throw new InvalidOperationException ();
+                       }
+               }
+
+               internal class XmlDsigXPathFunctionHere : IXsltContextFunction
+               {
+                       // Static
+
+                       static XPathResultType [] types;
+                       static XmlDsigXPathFunctionHere ()
+                       {
+                               types = new XPathResultType [0];
+                       }
+
+                       // Instance
+
+                       XPathNodeIterator xpathNode;
+
+                       public XmlDsigXPathFunctionHere (XmlNode node)
+                       {
+                               xpathNode = node.CreateNavigator ().Select (".");
+                       }
+
+                       public XPathResultType [] ArgTypes {
+                               get { return types; }
+                       }
+               
+                       public int Maxargs { get { return 0; } }
+               
+                       public int Minargs { get { return 0; } }
+               
+                       public XPathResultType ReturnType {
+                               get { return XPathResultType.NodeSet; }
+                       }
+
+                       public object Invoke (XsltContext ctx, object [] args, XPathNavigator docContext)
+                       {
+                               if (args.Length != 0)
+                                       throw new ArgumentException ("Not allowed arguments for function here().", "args");
 
-                       if (xn != null) {
-                               string xpath = xn.InnerXml;
-                               // only possible output: XmlNodeList
-                               xpathNodes = xnl[0].SelectNodes (xpath);
+                               return xpathNode.Clone ();
                        }
-                       else
-                               xpathNodes = null;
                }
        }
 }