rename net_2_1 to moonlight
[mcs.git] / class / System.Security / Mono.Xml / XmlCanonicalizer.cs
blobae267a93856c6fac3fe75814a225d151a4216aee
1 //
2 // XmlCanonicalizer.cs - C14N implementation for XML Signature
3 // http://www.w3.org/TR/xml-c14n
4 //
5 // Author:
6 // Aleksey Sanin (aleksey@aleksey.com)
7 //
8 // (C) 2003 Aleksey Sanin (aleksey@aleksey.com)
9 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Collections;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
37 namespace Mono.Xml {
39 internal class XmlCanonicalizer {
41 private enum XmlCanonicalizerState
43 BeforeDocElement,
44 InsideDocElement,
45 AfterDocElement
48 // c14n parameters
49 private bool comments;
50 private bool exclusive;
51 string inclusiveNamespacesPrefixList;
53 // input/output
54 private XmlNodeList xnl;
55 private StringBuilder res;
57 // namespaces rendering stack
58 private XmlCanonicalizerState state;
59 private ArrayList visibleNamespaces;
60 private int prevVisibleNamespacesStart;
61 private int prevVisibleNamespacesEnd;
62 private Hashtable propagatedNss;
64 public XmlCanonicalizer (bool withComments, bool excC14N, Hashtable propagatedNamespaces)
66 res = new StringBuilder ();
67 comments = withComments;
68 exclusive = excC14N;
69 propagatedNss = propagatedNamespaces;
72 void Initialize ()
74 state = XmlCanonicalizerState.BeforeDocElement;
75 visibleNamespaces = new ArrayList ();
76 prevVisibleNamespacesStart = 0;
77 prevVisibleNamespacesEnd = 0;
78 res.Length = 0;
81 public Stream Canonicalize (XmlDocument doc)
83 if (doc == null)
84 throw new ArgumentNullException ("doc");
85 Initialize ();
87 FillMissingPrefixes (doc, new XmlNamespaceManager (doc.NameTable), new ArrayList ());
88 WriteDocumentNode (doc);
90 UTF8Encoding utf8 = new UTF8Encoding ();
91 byte[] data = utf8.GetBytes (res.ToString ());
92 return new MemoryStream (data);
95 public Stream Canonicalize (XmlNodeList nodes)
97 xnl = nodes;
98 if (nodes == null || nodes.Count < 1)
99 return new MemoryStream ();
100 XmlNode n = nodes [0];
101 return Canonicalize (n.NodeType == XmlNodeType.Document ? n as XmlDocument : n.OwnerDocument);
104 // See xml-enc-c14n specification
105 public string InclusiveNamespacesPrefixList {
106 get { return inclusiveNamespacesPrefixList; }
107 set { inclusiveNamespacesPrefixList = value; }
110 XmlAttribute CreateXmlns (XmlNode n)
112 XmlAttribute a = n.Prefix.Length == 0 ?
113 n.OwnerDocument.CreateAttribute ("xmlns", "http://www.w3.org/2000/xmlns/") :
114 n.OwnerDocument.CreateAttribute ("xmlns", n.Prefix, "http://www.w3.org/2000/xmlns/");
115 a.Value = n.NamespaceURI;
116 return a;
119 // Note that this must be done *before* filtering nodes out
120 // by context node list.
121 private void FillMissingPrefixes (XmlNode n, XmlNamespaceManager nsmgr, ArrayList tmpList)
123 if (n.Prefix.Length == 0 && propagatedNss != null) {
124 foreach (DictionaryEntry de in propagatedNss)
125 if ((string) de.Value == n.NamespaceURI) {
126 n.Prefix = (string) de.Key;
127 break;
131 if (n.NodeType == XmlNodeType.Element && ((XmlElement) n).HasAttributes) {
132 foreach (XmlAttribute a in n.Attributes)
133 if (a.NamespaceURI == "http://www.w3.org/2000/xmlns/")
134 nsmgr.AddNamespace (a.Prefix.Length == 0 ? String.Empty : a.LocalName, a.Value);
135 nsmgr.PushScope ();
138 if (n.NamespaceURI.Length > 0 && nsmgr.LookupPrefix (n.NamespaceURI) == null)
139 tmpList.Add (CreateXmlns (n));
141 if (n.NodeType == XmlNodeType.Element && ((XmlElement) n).HasAttributes) {
142 foreach (XmlAttribute a in n.Attributes)
143 if (a.NamespaceURI.Length > 0 && nsmgr.LookupNamespace (a.Prefix) == null)
144 tmpList.Add (CreateXmlns (a));
147 foreach (XmlAttribute a in tmpList)
148 ((XmlElement) n).SetAttributeNode (a);
149 tmpList.Clear ();
151 if (n.HasChildNodes) {
152 for (XmlNode c = n.FirstChild; c != null; c = c.NextSibling)
153 if (c.NodeType == XmlNodeType.Element)
154 FillMissingPrefixes (c, nsmgr, tmpList);
156 nsmgr.PopScope ();
159 private void WriteNode (XmlNode node)
161 // Console.WriteLine ("C14N Debug: node=" + node.Name);
163 bool visible = IsNodeVisible (node);
164 switch (node.NodeType) {
165 case XmlNodeType.Document:
166 case XmlNodeType.DocumentFragment:
167 WriteDocumentNode (node);
168 break;
169 case XmlNodeType.Element:
170 WriteElementNode (node, visible);
171 break;
172 case XmlNodeType.CDATA:
173 case XmlNodeType.SignificantWhitespace:
174 case XmlNodeType.Text:
175 // CDATA sections are processed as text nodes
176 WriteTextNode (node, visible);
177 break;
178 case XmlNodeType.Whitespace:
179 if (state == XmlCanonicalizerState.InsideDocElement)
180 WriteTextNode (node, visible);
181 break;
182 case XmlNodeType.Comment:
183 WriteCommentNode (node, visible);
184 break;
185 case XmlNodeType.ProcessingInstruction:
186 WriteProcessingInstructionNode (node, visible);
187 break;
188 case XmlNodeType.EntityReference:
189 for (int i = 0; i < node.ChildNodes.Count; i++)
190 WriteNode (node.ChildNodes [i]);
191 break;
192 case XmlNodeType.Attribute:
193 throw new XmlException ("Attribute node is impossible here", null);
194 case XmlNodeType.EndElement:
195 throw new XmlException ("EndElement node is impossible here", null);
196 case XmlNodeType.EndEntity:
197 throw new XmlException ("EndEntity node is impossible here", null);
198 case XmlNodeType.DocumentType:
199 case XmlNodeType.Entity:
200 case XmlNodeType.Notation:
201 case XmlNodeType.XmlDeclaration:
202 // just do nothing
203 break;
207 private void WriteDocumentNode (XmlNode node)
209 state = XmlCanonicalizerState.BeforeDocElement;
210 for (XmlNode child = node.FirstChild; child != null; child = child.NextSibling)
211 WriteNode (child);
214 // Element Nodes
215 // If the element is not in the node-set, then the result is obtained
216 // by processing the namespace axis, then the attribute axis, then
217 // processing the child nodes of the element that are in the node-set
218 // (in document order). If the element is inthe node-set, then the result
219 // is an open angle bracket (<), the element QName, the result of
220 // processing the namespace axis, the result of processing the attribute
221 // axis, a close angle bracket (>), the result of processing the child
222 // nodes of the element that are in the node-set (in document order), an
223 // open angle bracket, a forward slash (/), the element QName, and a close
224 // angle bracket.
225 private void WriteElementNode (XmlNode node, bool visible)
227 // Console.WriteLine ("Debug: element node");
229 // remember current state
230 int savedPrevVisibleNamespacesStart = prevVisibleNamespacesStart;
231 int savedPrevVisibleNamespacesEnd = prevVisibleNamespacesEnd;
232 int savedVisibleNamespacesSize = visibleNamespaces.Count;
233 XmlCanonicalizerState s = state;
234 if (visible && state == XmlCanonicalizerState.BeforeDocElement)
235 state = XmlCanonicalizerState.InsideDocElement;
237 // write start tag
238 if (visible) {
239 res.Append ("<");
240 res.Append (node.Name);
243 // this is odd but you can select namespaces
244 // and attributes even if node itself is not visible
245 WriteNamespacesAxis (node, visible);
246 WriteAttributesAxis (node);
248 if (visible)
249 res.Append (">");
251 // write children
252 for (XmlNode child = node.FirstChild; child != null; child = child.NextSibling)
253 WriteNode (child);
255 // write end tag
256 if (visible) {
257 res.Append ("</");
258 res.Append (node.Name);
259 res.Append (">");
262 // restore state
263 if (visible && s == XmlCanonicalizerState.BeforeDocElement)
264 state = XmlCanonicalizerState.AfterDocElement;
265 prevVisibleNamespacesStart = savedPrevVisibleNamespacesStart;
266 prevVisibleNamespacesEnd = savedPrevVisibleNamespacesEnd;
267 if (visibleNamespaces.Count > savedVisibleNamespacesSize) {
268 visibleNamespaces.RemoveRange (savedVisibleNamespacesSize,
269 visibleNamespaces.Count - savedVisibleNamespacesSize);
273 // Namespace Axis
274 // Consider a list L containing only namespace nodes in the
275 // axis and in the node-set in lexicographic order (ascending). To begin
276 // processing L, if the first node is not the default namespace node (a node
277 // with no namespace URI and no local name), then generate a space followed
278 // by xmlns="" if and only if the following conditions are met:
279 // - the element E that owns the axis is in the node-set
280 // - The nearest ancestor element of E in the node-set has a default
281 // namespace node in the node-set (default namespace nodes always
282 // have non-empty values in XPath)
283 // The latter condition eliminates unnecessary occurrences of xmlns="" in
284 // the canonical form since an element only receives an xmlns="" if its
285 // default namespace is empty and if it has an immediate parent in the
286 // canonical form that has a non-empty default namespace. To finish
287 // processing L, simply process every namespace node in L, except omit
288 // namespace node with local name xml, which defines the xml prefix,
289 // if its string value is http://www.w3.org/XML/1998/namespace.
290 private void WriteNamespacesAxis (XmlNode node, bool visible)
292 // Console.WriteLine ("Debug: namespaces");
294 XmlDocument doc = node.OwnerDocument;
295 bool has_empty_namespace = false;
296 ArrayList list = new ArrayList ();
297 for (XmlNode cur = node; cur != null && cur != doc; cur = cur.ParentNode) {
298 foreach (XmlAttribute attribute in cur.Attributes) {
299 if (!IsNamespaceNode (attribute))
300 continue;
302 // get namespace prefix
303 string prefix = string.Empty;
304 if (attribute.Prefix == "xmlns")
305 prefix = attribute.LocalName;
307 // check if it is "xml" namespace
308 if (prefix == "xml" && attribute.Value == "http://www.w3.org/XML/1998/namespace")
309 continue;
311 // make sure that this is an active namespace
312 // for our node
313 string ns = node.GetNamespaceOfPrefix (prefix);
314 if (ns != attribute.Value)
315 continue;
317 // check that it is selected with XPath
318 if (!IsNodeVisible (attribute))
319 continue;
321 // check that we have not rendered it yet
322 bool rendered = IsNamespaceRendered (prefix, attribute.Value);
324 // For exc-c14n, only visibly utilized
325 // namespaces are written.
326 if (exclusive && !IsVisiblyUtilized (node as XmlElement, attribute))
327 continue;
329 // add to the visible namespaces stack
330 if (visible)
331 visibleNamespaces.Add (attribute);
333 if (!rendered)
334 list.Add (attribute);
336 if (prefix == string.Empty)
337 has_empty_namespace = true;
341 // add empty namespace if needed
342 if (visible && !has_empty_namespace && !IsNamespaceRendered (string.Empty, string.Empty) && node.NamespaceURI == String.Empty)
343 res.Append (" xmlns=\"\"");
345 list.Sort (new XmlDsigC14NTransformNamespacesComparer ());
346 foreach (object obj in list) {
347 XmlNode attribute = (obj as XmlNode);
348 if (attribute != null) {
349 res.Append (" ");
350 res.Append (attribute.Name);
351 res.Append ("=\"");
352 res.Append (attribute.Value);
353 res.Append ("\"");
357 // move the rendered namespaces stack
358 if (visible) {
359 prevVisibleNamespacesStart = prevVisibleNamespacesEnd;
360 prevVisibleNamespacesEnd = visibleNamespaces.Count;
364 // Attribute Axis
365 // In lexicographic order (ascending), process each node that
366 // is in the element's attribute axis and in the node-set.
368 // The processing of an element node E MUST be modified slightly
369 // when an XPath node-set is given as input and the element's
370 // parent is omitted from the node-set.
371 private void WriteAttributesAxis (XmlNode node)
373 // Console.WriteLine ("Debug: attributes");
375 ArrayList list = new ArrayList ();
376 foreach (XmlNode attribute in node.Attributes) {
377 if (!IsNamespaceNode (attribute) && IsNodeVisible (attribute))
378 list.Add (attribute);
381 // Add attributes from "xml" namespace for "inclusive" c14n only:
383 // The method for processing the attribute axis of an element E
384 // in the node-set is enhanced. All element nodes along E's
385 // ancestor axis are examined for nearest occurrences of
386 // attributes in the xml namespace, such as xml:lang and
387 // xml:space (whether or not they are in the node-set).
388 // From this list of attributes, remove any that are in E's
389 // attribute axis (whether or not they are in the node-set).
390 // Then, lexicographically merge this attribute list with the
391 // nodes of E's attribute axis that are in the node-set. The
392 // result of visiting the attribute axis is computed by
393 // processing the attribute nodes in this merged attribute list.
394 if (!exclusive && node.ParentNode != null && node.ParentNode.ParentNode != null && !IsNodeVisible (node.ParentNode.ParentNode)) {
395 // if we have whole document then the node.ParentNode.ParentNode
396 // is always visible
397 for (XmlNode cur = node.ParentNode; cur != null; cur = cur.ParentNode) {
398 if (cur.Attributes == null)
399 continue;
400 foreach (XmlNode attribute in cur.Attributes) {
401 // we are looking for "xml:*" attributes
402 if (attribute.Prefix != "xml")
403 continue;
405 // exclude ones that are in the node's attributes axis
406 if (node.Attributes.GetNamedItem (attribute.LocalName, attribute.NamespaceURI) != null)
407 continue;
409 // finally check that we don't have the same attribute in our list
410 bool found = false;
411 foreach (object obj in list) {
412 XmlNode n = (obj as XmlNode);
413 if (n.Prefix == "xml" && n.LocalName == attribute.LocalName) {
414 found = true;
415 break;
419 if (found)
420 continue;
422 // now we can add this attribute to our list
423 list.Add (attribute);
428 // sort namespaces and write results
429 list.Sort (new XmlDsigC14NTransformAttributesComparer ());
430 foreach (object obj in list) {
431 XmlNode attribute = (obj as XmlNode);
432 if (attribute != null) {
433 res.Append (" ");
434 res.Append (attribute.Name);
435 res.Append ("=\"");
436 res.Append (NormalizeString (attribute.Value, XmlNodeType.Attribute));
437 res.Append ("\"");
442 // Text Nodes
443 // the string value, except all ampersands are replaced
444 // by &amp;, all open angle brackets (<) are replaced by &lt;, all closing
445 // angle brackets (>) are replaced by &gt;, and all #xD characters are
446 // replaced by &#xD;.
447 private void WriteTextNode (XmlNode node, bool visible)
449 // Console.WriteLine ("Debug: text node");
450 if (visible)
451 res.Append (NormalizeString (node.Value, node.NodeType));
452 // res.Append (NormalizeString (node.Value, XmlNodeType.Text));
455 // Comment Nodes
456 // Nothing if generating canonical XML without comments. For
457 // canonical XML with comments, generate the opening comment
458 // symbol (<!--), the string value of the node, and the
459 // closing comment symbol (-->). Also, a trailing #xA is rendered
460 // after the closing comment symbol for comment children of the
461 // root node with a lesser document order than the document
462 // element, and a leading #xA is rendered before the opening
463 // comment symbol of comment children of the root node with a
464 // greater document order than the document element. (Comment
465 // children of the root node represent comments outside of the
466 // top-level document element and outside of the document type
467 // declaration).
468 private void WriteCommentNode (XmlNode node, bool visible)
470 // Console.WriteLine ("Debug: comment node");
471 if (visible && comments) {
472 if (state == XmlCanonicalizerState.AfterDocElement)
473 res.Append ("\x0A<!--");
474 else
475 res.Append ("<!--");
477 res.Append (NormalizeString (node.Value, XmlNodeType.Comment));
479 if (state == XmlCanonicalizerState.BeforeDocElement)
480 res.Append ("-->\x0A");
481 else
482 res.Append ("-->");
486 // Processing Instruction (PI) Nodes-
487 // The opening PI symbol (<?), the PI target name of the node,
488 // a leading space and the string value if it is not empty, and
489 // the closing PI symbol (?>). If the string value is empty,
490 // then the leading space is not added. Also, a trailing #xA is
491 // rendered after the closing PI symbol for PI children of the
492 // root node with a lesser document order than the document
493 // element, and a leading #xA is rendered before the opening PI
494 // symbol of PI children of the root node with a greater document
495 // order than the document element.
496 private void WriteProcessingInstructionNode (XmlNode node, bool visible)
498 // Console.WriteLine ("Debug: PI node");
500 if (visible) {
501 if (state == XmlCanonicalizerState.AfterDocElement)
502 res.Append ("\x0A<?");
503 else
504 res.Append ("<?");
506 res.Append (node.Name);
507 if (node.Value.Length > 0) {
508 res.Append (" ");
509 res.Append (NormalizeString (node.Value, XmlNodeType.ProcessingInstruction));
512 if (state == XmlCanonicalizerState.BeforeDocElement)
513 res.Append ("?>\x0A");
514 else
515 res.Append ("?>");
519 // determines whether the node is in the node-set or not.
520 private bool IsNodeVisible (XmlNode node)
522 // if node list is empty then we process whole document
523 if (xnl == null)
524 return true;
526 // walk thru the list
527 foreach (XmlNode xn in xnl) {
528 if (node.Equals (xn))
529 return true;
532 return false;
535 // This method assumes that the namespace node is *not*
536 // rendered yet.
537 private bool IsVisiblyUtilized (XmlElement owner, XmlAttribute ns)
539 if (owner == null)
540 return false;
542 string prefix = ns.LocalName == "xmlns" ? String.Empty : ns.LocalName;
543 if (owner.Prefix == prefix && owner.NamespaceURI == ns.Value)
544 return true;
545 if (!owner.HasAttributes)
546 return false;
547 foreach (XmlAttribute a in owner.Attributes) {
548 if (a.Prefix == String.Empty)
549 continue;
550 if (a.Prefix != prefix || a.NamespaceURI != ns.Value)
551 continue;
552 if (IsNodeVisible (a))
553 return true;
555 return false;
558 private bool IsNamespaceRendered (string prefix, string uri)
560 // if the default namespace xmlns="" is not re-defined yet
561 // then we do not want to print it out
562 bool IsEmptyNs = prefix == string.Empty && uri == string.Empty;
563 int start = (IsEmptyNs) ? 0 : prevVisibleNamespacesStart;
564 for (int i = visibleNamespaces.Count - 1; i >= start; i--) {
565 XmlNode node = (visibleNamespaces[i] as XmlNode);
566 if (node != null) {
567 // get namespace prefix
568 string p = string.Empty;
569 if (node.Prefix == "xmlns")
570 p = node.LocalName;
571 if (p == prefix)
572 return node.Value == uri;
576 return IsEmptyNs;
579 private bool IsNamespaceNode (XmlNode node)
581 if (node == null || node.NodeType != XmlNodeType.Attribute)
582 return false;
583 return node.NamespaceURI == "http://www.w3.org/2000/xmlns/";
586 private bool IsTextNode (XmlNodeType type)
588 switch (type) {
589 case XmlNodeType.Text:
590 case XmlNodeType.CDATA:
591 case XmlNodeType.SignificantWhitespace:
592 case XmlNodeType.Whitespace:
593 return true;
595 return false;
598 private string NormalizeString (string input, XmlNodeType type)
600 StringBuilder sb = new StringBuilder ();
601 for (int i = 0; i < input.Length; i++) {
602 char ch = input[i];
603 if (ch == '<' && (type == XmlNodeType.Attribute || IsTextNode (type)))
604 sb.Append ("&lt;");
605 else if (ch == '>' && IsTextNode (type))
606 sb.Append ("&gt;");
607 else if (ch == '&' && (type == XmlNodeType.Attribute || IsTextNode (type)))
608 sb.Append ("&amp;");
609 else if (ch == '\"' && type == XmlNodeType.Attribute)
610 sb.Append ("&quot;");
611 else if (ch == '\x09' && type == XmlNodeType.Attribute)
612 sb.Append ("&#x9;");
613 else if (ch == '\x0A' && type == XmlNodeType.Attribute)
614 sb.Append ("&#xA;");
615 else if (ch == '\x0D')
616 sb.Append ("&#xD;");
617 else
618 sb.Append (ch);
621 return sb.ToString ();
625 internal class XmlDsigC14NTransformAttributesComparer : IComparer
627 public int Compare (object x, object y)
629 XmlNode n1 = (x as XmlNode);
630 XmlNode n2 = (y as XmlNode);
632 // simple cases
633 if (n1 == n2)
634 return 0;
635 else if (n1 == null)
636 return -1;
637 else if (n2 == null)
638 return 1;
639 else if (n1.Prefix == n2.Prefix)
640 return string.Compare (n1.LocalName, n2.LocalName);
642 // Attributes in the default namespace are first
643 // because the default namespace is not applied to
644 // unqualified attributes
645 if (n1.Prefix == string.Empty)
646 return -1;
647 else if (n2.Prefix == string.Empty)
648 return 1;
650 int ret = string.Compare (n1.NamespaceURI, n2.NamespaceURI);
651 if (ret == 0)
652 ret = string.Compare (n1.LocalName, n2.LocalName);
653 return ret;
657 internal class XmlDsigC14NTransformNamespacesComparer : IComparer
659 public int Compare (object x, object y)
661 XmlNode n1 = (x as XmlNode);
662 XmlNode n2 = (y as XmlNode);
664 // simple cases
665 if (n1 == n2)
666 return 0;
667 else if (n1 == null)
668 return -1;
669 else if (n2 == null)
670 return 1;
671 else if (n1.Prefix == string.Empty)
672 return -1;
673 else if (n2.Prefix == string.Empty)
674 return 1;
676 return string.Compare (n1.LocalName, n2.LocalName);