2004-03-16 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                         return doc.SelectNodes (x, ctx);
98                         ctx = new XmlDsigXPathContext (doc);
99                         return EvaluateMatch (doc, x);
100                 }
101
102                 public override object GetOutput (Type type) 
103                 {
104                         if (type != typeof (XmlNodeList))
105                                 throw new ArgumentException ("type");
106                         return GetOutput ();
107                 }
108
109                 private XmlDsigNodeList EvaluateMatch (XmlNode n, string xpath)
110                 {
111                         ArrayList al = new ArrayList ();
112                         // Strictly to say, document node is explicitly
113                         // excluded by W3C spec (context node is initialized
114                         // to the document root and XPath expression is
115                         // "//. | //@* | //namespace::*)
116                         XPathNavigator nav = n.CreateNavigator ();
117                         XPathExpression exp = nav.Compile (xpath);
118                         exp.SetContext (ctx);
119                         EvaluateMatch (n, exp, al);
120                         return new XmlDsigNodeList (al);
121                 }
122
123                 private void EvaluateMatch (XmlNode n, XPathExpression exp, ArrayList al)
124                 {
125                         if (NodeMatches (n, exp))
126                                 al.Add (n);
127                         if (n.Attributes != null)
128                                 for (int i = 0; i < n.Attributes.Count; i++)
129                                         EvaluateMatch (n.Attributes [i], exp, al);
130                         for (int i = 0; i < n.ChildNodes.Count; i++)
131                                 EvaluateMatch (n.ChildNodes [i], exp, al);
132                 }
133
134                 private bool NodeMatches (XmlNode n, XPathExpression exp)
135                 {
136                         // This looks waste of memory since it creates 
137                         // XPathNavigator every time, but even if we use
138                         //  XPathNodeIterator.Current, it also clones every time.
139                         object ret = n.CreateNavigator ().Evaluate (exp);
140                         if (ret is bool)
141                                 return (bool) ret;
142                         if (ret is double) {
143                                 double d = (double) ret;
144                                 return !(d == 0.0 || d == double.NaN);
145                         }
146                         if (ret is string)
147                                 return ((string) ret).Length > 0;
148                         if (ret is XPathNodeIterator) {
149                                 XPathNodeIterator retiter = (XPathNodeIterator) ret;
150                                 return retiter.Count > 0;
151                         }
152                         return false;
153                 }
154
155                 public override void LoadInnerXml (XmlNodeList nodeList) 
156                 {
157                         if (nodeList == null)
158                                 throw new CryptographicException ("nodeList");
159                         xpath = nodeList;
160                 }
161
162                 public override void LoadInput (object obj) 
163                 {
164                         // possible input: Stream, XmlDocument, and XmlNodeList
165                         if (obj is Stream) {
166                                 doc = new XmlDocument ();
167                                 doc.XmlResolver = GetResolver ();
168                                 doc.Load (obj as Stream);
169                         }
170                         else if (obj is XmlDocument) {
171                                 doc = (obj as XmlDocument);
172                         }
173                         else if (obj is XmlNodeList) {
174                                 doc = new XmlDocument ();
175                                 doc.XmlResolver = GetResolver ();
176                                 foreach (XmlNode xn in (obj as XmlNodeList))  {
177                                         XmlNode importedNode = doc.ImportNode (xn, true);
178                                         doc.AppendChild (importedNode);
179                                 }
180                         }
181                 }
182
183                 // Internal classes to support XPath extension function here()
184
185                 internal class XmlDsigXPathContext : XsltContext
186                 {
187                         XmlDsigXPathFunctionHere here;
188                         public XmlDsigXPathContext (XmlNode node)
189                         {
190                                 here = new XmlDsigXPathFunctionHere (node);
191                         }
192
193                         public override IXsltContextFunction ResolveFunction (
194                                 string prefix, string name, XPathResultType [] argType)
195                         {
196                                 // Here MS.NET incorrectly allows arbitrary
197                                 // name e.g. "heretic()".
198                                 if (name == "here" &&
199                                         prefix == String.Empty &&
200                                         argType.Length == 0)
201                                         return here;
202                                 else
203                                         return null; // ????
204                         }
205
206                         public override bool Whitespace {
207                                 get { return true; }
208                         }
209
210                         public override bool PreserveWhitespace (XPathNavigator node)
211                         {
212                                 return true;
213                         }
214
215                         public override int CompareDocument (string s1, string s2)
216                         {
217                                 return String.Compare (s1, s2);
218                         }
219
220                         public override IXsltContextVariable ResolveVariable (string prefix, string name)
221                         {
222                                 throw new InvalidOperationException ();
223                         }
224                 }
225
226                 internal class XmlDsigXPathFunctionHere : IXsltContextFunction
227                 {
228                         // Static
229
230                         static XPathResultType [] types;
231                         static XmlDsigXPathFunctionHere ()
232                         {
233                                 types = new XPathResultType [0];
234                         }
235
236                         // Instance
237
238                         XPathNodeIterator xpathNode;
239
240                         public XmlDsigXPathFunctionHere (XmlNode node)
241                         {
242                                 xpathNode = node.CreateNavigator ().Select (".");
243                         }
244
245                         public XPathResultType [] ArgTypes {
246                                 get { return types; }
247                         }
248                 
249                         public int Maxargs { get { return 0; } }
250                 
251                         public int Minargs { get { return 0; } }
252                 
253                         public XPathResultType ReturnType {
254                                 get { return XPathResultType.NodeSet; }
255                         }
256
257                         public object Invoke (XsltContext ctx, object [] args, XPathNavigator docContext)
258                         {
259                                 if (args.Length != 0)
260                                         throw new ArgumentException ("Not allowed arguments for function here().", "args");
261
262                                 return xpathNode.Clone ();
263                         }
264                 }
265         }
266 }