2004-03-26 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Security / System.Security.Cryptography.Xml / XmlDsigXPathTransform.cs
1 //
2 // XmlDsigXPathTransform.cs - 
3 //      XmlDsigXPathTransform implementation for XML Signature
4 // http://www.w3.org/TR/1999/REC-xpath-19991116 
5 //
6 // Author:
7 //      Sebastien Pouliot <sebastien@ximian.com>
8 //      Atsushi Enomoto <atsushi@ximian.com>
9 //
10 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
11 // (C) 2004 Novell (http://www.novell.com)
12 //
13
14 using System.Collections;
15 using System.IO;
16 using System.Text;
17 using System.Xml;
18 using System.Xml.XPath;
19 using System.Xml.Xsl;
20
21 namespace System.Security.Cryptography.Xml 
22 {
23
24         // www.w3.org/TR/xmldsig-core/
25         // see Section 6.6.3 of the XMLDSIG specification
26         public class XmlDsigXPathTransform : Transform 
27         {
28
29                 private Type [] input;
30                 private Type [] output;
31                 private XmlNodeList xpath;
32                 private XmlDocument doc;
33                 private XsltContext ctx;
34
35                 public XmlDsigXPathTransform () 
36                 {
37                         Algorithm = "http://www.w3.org/TR/1999/REC-xpath-19991116";
38                 }
39
40                 public override Type [] InputTypes {
41                         get {
42                                 if (input == null) {
43                                         lock (this) {
44                                                 // this way the result is cached if called multiple time
45                                                 input = new Type [3];
46                                                 input [0] = typeof (System.IO.Stream);
47                                                 input [1] = typeof (System.Xml.XmlDocument);
48                                                 input [2] = typeof (System.Xml.XmlNodeList);
49                                         }
50                                 }
51                                 return input;
52                         }
53                 }
54
55                 public override Type[] OutputTypes {
56                         get {
57                                 if (output == null) {
58                                         lock (this) {
59                                                 // this way the result is cached if called multiple time
60                                                 output = new Type [1];
61                                                 output [0] = typeof (System.Xml.XmlNodeList);
62                                         }
63                                 }
64                                 return output;
65                         }
66                 }
67
68                 protected override XmlNodeList GetInnerXml () 
69                 {
70                         if (xpath == null) {
71                                 // default value
72                                 XmlDocument xpdoc = new XmlDocument ();
73                                 xpdoc.LoadXml ("<XPath xmlns=\"" + XmlSignature.NamespaceURI + "\"></XPath>");
74                                 xpath = xpdoc.ChildNodes;
75                         }
76                         return xpath;
77                 }
78
79                 [MonoTODO ("Evaluation of extension function here() results in different from MS.NET (is MS.NET really correct??).")]
80                 public override object GetOutput () 
81                 {
82                         if (xpath == null)
83                                 return new XmlDsigNodeList (new ArrayList ());
84
85                         // evaluate every time since input or xpath might have changed.
86                         string x = null;
87                         for (int i = 0; i < xpath.Count; i++) {
88                                 switch (xpath [i].NodeType) {
89                                 case XmlNodeType.Text:
90                                 case XmlNodeType.CDATA:
91                                 case XmlNodeType.Element:
92                                         x += xpath [i].InnerText;
93                                         break;
94                                 }
95                         }
96
97                         ctx = new XmlDsigXPathContext (doc);
98                         foreach (XmlNode n in xpath) {
99                                 XPathNavigator nav = n.CreateNavigator ();
100                                 XPathNodeIterator iter = nav.Select ("namespace::*");
101                                 while (iter.MoveNext ())
102                                         if (iter.Current.LocalName != "xml")
103                                                 ctx.AddNamespace (iter.Current.LocalName, iter.Current.Value);
104                         }
105                         return EvaluateMatch (doc, x);
106                 }
107
108                 public override object GetOutput (Type type) 
109                 {
110                         if (type != typeof (XmlNodeList))
111                                 throw new ArgumentException ("type");
112                         return GetOutput ();
113                 }
114
115                 private XmlDsigNodeList EvaluateMatch (XmlNode n, string xpath)
116                 {
117                         ArrayList al = new ArrayList ();
118                         // Strictly to say, document node is explicitly
119                         // excluded by W3C spec (context node is initialized
120                         // to the document root and XPath expression is
121                         // "//. | //@* | //namespace::*)
122                         XPathNavigator nav = n.CreateNavigator ();
123                         XPathExpression exp = nav.Compile (xpath);
124                         exp.SetContext (ctx);
125                         EvaluateMatch (n, exp, al);
126                         return new XmlDsigNodeList (al);
127                 }
128
129                 private void EvaluateMatch (XmlNode n, XPathExpression exp, ArrayList al)
130                 {
131                         if (NodeMatches (n, exp))
132                                 al.Add (n);
133                         if (n.Attributes != null)
134                                 for (int i = 0; i < n.Attributes.Count; i++)
135                                         if (NodeMatches (n.Attributes [i], exp))
136                                                 al.Add (n.Attributes [i]);
137                         for (int i = 0; i < n.ChildNodes.Count; i++)
138                                 EvaluateMatch (n.ChildNodes [i], exp, al);
139                 }
140
141                 private bool NodeMatches (XmlNode n, XPathExpression exp)
142                 {
143                         // This looks waste of memory since it creates 
144                         // XPathNavigator every time, but even if we use
145                         //  XPathNodeIterator.Current, it also clones every time.
146                         object ret = n.CreateNavigator ().Evaluate (exp);
147                         if (ret is bool)
148                                 return (bool) ret;
149                         if (ret is double) {
150                                 double d = (double) ret;
151                                 return !(d == 0.0 || d == double.NaN);
152                         }
153                         if (ret is string)
154                                 return ((string) ret).Length > 0;
155                         if (ret is XPathNodeIterator) {
156                                 XPathNodeIterator retiter = (XPathNodeIterator) ret;
157                                 return retiter.Count > 0;
158                         }
159                         return false;
160                 }
161
162                 public override void LoadInnerXml (XmlNodeList nodeList) 
163                 {
164                         if (nodeList == null)
165                                 throw new CryptographicException ("nodeList");
166                         xpath = nodeList;
167                 }
168
169                 public override void LoadInput (object obj) 
170                 {
171                         // possible input: Stream, XmlDocument, and XmlNodeList
172                         if (obj is Stream) {
173                                 doc = new XmlDocument ();
174                                 doc.XmlResolver = GetResolver ();
175                                 doc.Load (obj as Stream);
176                         }
177                         else if (obj is XmlDocument) {
178                                 doc = (obj as XmlDocument);
179                         }
180                         else if (obj is XmlNodeList) {
181                                 doc = new XmlDocument ();
182                                 doc.XmlResolver = GetResolver ();
183                                 foreach (XmlNode xn in (obj as XmlNodeList))  {
184                                         XmlNode importedNode = doc.ImportNode (xn, true);
185                                         doc.AppendChild (importedNode);
186                                 }
187                         }
188                 }
189
190                 // Internal classes to support XPath extension function here()
191
192                 internal class XmlDsigXPathContext : XsltContext
193                 {
194                         XmlDsigXPathFunctionHere here;
195                         public XmlDsigXPathContext (XmlNode node)
196                         {
197                                 here = new XmlDsigXPathFunctionHere (node);
198                         }
199
200                         public override IXsltContextFunction ResolveFunction (
201                                 string prefix, string name, XPathResultType [] argType)
202                         {
203                                 // Here MS.NET incorrectly allows arbitrary
204                                 // name e.g. "heretic()".
205                                 if (name == "here" &&
206                                         prefix == String.Empty &&
207                                         argType.Length == 0)
208                                         return here;
209                                 else
210                                         return null; // ????
211                         }
212
213                         public override bool Whitespace {
214                                 get { return true; }
215                         }
216
217                         public override bool PreserveWhitespace (XPathNavigator node)
218                         {
219                                 return true;
220                         }
221
222                         public override int CompareDocument (string s1, string s2)
223                         {
224                                 return String.Compare (s1, s2);
225                         }
226
227                         public override IXsltContextVariable ResolveVariable (string prefix, string name)
228                         {
229                                 throw new InvalidOperationException ();
230                         }
231                 }
232
233                 internal class XmlDsigXPathFunctionHere : IXsltContextFunction
234                 {
235                         // Static
236
237                         static XPathResultType [] types;
238                         static XmlDsigXPathFunctionHere ()
239                         {
240                                 types = new XPathResultType [0];
241                         }
242
243                         // Instance
244
245                         XPathNodeIterator xpathNode;
246
247                         public XmlDsigXPathFunctionHere (XmlNode node)
248                         {
249                                 xpathNode = node.CreateNavigator ().Select (".");
250                         }
251
252                         public XPathResultType [] ArgTypes {
253                                 get { return types; }
254                         }
255                 
256                         public int Maxargs { get { return 0; } }
257                 
258                         public int Minargs { get { return 0; } }
259                 
260                         public XPathResultType ReturnType {
261                                 get { return XPathResultType.NodeSet; }
262                         }
263
264                         public object Invoke (XsltContext ctx, object [] args, XPathNavigator docContext)
265                         {
266                                 if (args.Length != 0)
267                                         throw new ArgumentException ("Not allowed arguments for function here().", "args");
268
269                                 return xpathNode.Clone ();
270                         }
271                 }
272         }
273 }