Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / corlib / System.Security / SecurityElement.cs
1 //
2 // System.Security.SecurityElement.cs
3 //
4 // Authors:
5 //      Miguel de Icaza (miguel@ximian.com)
6 //      Lawrence Pit (loz@cable.a2000.nl)
7 //      Sebastien Pouliot  <sebastien@ximian.com>
8 //
9 // (C) Ximian, Inc. http://www.ximian.com
10 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 //
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.
30 //
31
32 using System.Globalization;
33 using System.Collections;
34 using System.Runtime.InteropServices;
35 using System.Text;
36
37 using Mono.Xml;
38
39 namespace System.Security {
40
41         [ComVisible (true)]
42         [Serializable]
43         public sealed class SecurityElement 
44         {
45                 internal class SecurityAttribute {
46                         
47                         private string _name;
48                         private string _value;
49
50                         public SecurityAttribute (string name, string value) 
51                         {
52                                 if (!IsValidAttributeName (name))
53                                         throw new ArgumentException (Locale.GetText ("Invalid XML attribute name") + ": " + name);
54
55                                 if (!IsValidAttributeValue (value))
56                                         throw new ArgumentException (Locale.GetText ("Invalid XML attribute value") + ": " + value);
57
58                                 _name = name;
59                                 _value = SecurityElement.Unescape (value);
60                         }
61
62                         public string Name {
63                                 get { return _name; }
64                         }
65
66                         public string Value {
67                                 get { return _value; }
68                         }
69                 }
70
71                 string text;
72                 string tag;
73                 ArrayList attributes;
74                 ArrayList children;
75                 
76                 // these values are determined by a simple test program against the MS.Net implementation:
77                 //      for (int i = 0; i < 256; i++) {
78                 //              if (!SecurityElement.IsValidTag ("" + ((char) i))) {
79                 //                      System.Console.WriteLine ("TAG: " + i);
80                 //              }
81                 //      }               
82                 // note: this is actually an incorrect implementation of MS, as for example the &
83                 // character is not a valid character in tag names.
84                 private static readonly char [] invalid_tag_chars = new char [] { ' ', '<', '>' };
85                 private static readonly char [] invalid_text_chars = new char [] { '<', '>' };
86                 private static readonly char [] invalid_attr_name_chars = new char [] { ' ', '<', '>' };
87                 private static readonly char [] invalid_attr_value_chars = new char [] { '"', '<', '>' };
88                 private static readonly char [] invalid_chars = new char [] { '<', '>', '"', '\'', '&' };
89                 
90                 public SecurityElement (string tag) : this (tag, null)
91                 {
92                 }
93                 
94                 public SecurityElement (string tag, string text)
95                 {
96                         if (tag == null)
97                                 throw new ArgumentNullException ("tag");
98                         if (!IsValidTag (tag))
99                                 throw new ArgumentException (Locale.GetText ("Invalid XML string") + ": " + tag);
100                         this.tag = tag;
101
102                         Text = text;
103                 }
104
105                 // not a deep copy (childs are references)
106                 internal SecurityElement (SecurityElement se)
107                 {
108                         this.Tag = se.Tag;
109                         this.Text = se.Text;
110
111                         if (se.attributes != null) {
112                                 foreach (SecurityAttribute sa in se.attributes) {
113                                         this.AddAttribute (sa.Name, sa.Value);
114                                 }
115                         }
116                         if (se.children != null) {
117                                 foreach (SecurityElement child in se.children) {
118                                         this.AddChild (child);
119                                 }
120                         }
121                 }
122                 
123                 public Hashtable Attributes {
124                         get {
125                                 if (attributes == null) 
126                                         return null;
127                                         
128                                 Hashtable result = new Hashtable (attributes.Count);
129                                 foreach (SecurityAttribute sa in attributes) {
130                                         result.Add (sa.Name, sa.Value);
131                                 }
132                                 return result;
133                         }
134
135                         set {
136                                 if (value == null || value.Count == 0) {
137                                         attributes.Clear ();
138                                         return;
139                                 }
140                                 
141                                 if (attributes == null)
142                                         attributes = new ArrayList ();
143                                 else
144                                         attributes.Clear ();
145                                 IDictionaryEnumerator e = value.GetEnumerator ();
146                                 while (e.MoveNext ()) {
147                                         attributes.Add (new SecurityAttribute ((string) e.Key, (string) e.Value));
148                                 }
149                         }
150                 }
151
152                 public ArrayList Children {
153                         get {
154                                 return children;
155                         }
156
157                         set {
158                                 if (value != null) {
159                                         foreach (object o in value) {
160                                                 if (o == null)
161                                                         throw new ArgumentNullException ();
162                                                 // shouldn't we also throw an exception 
163                                                 // when o isn't an instance of SecurityElement?
164                                         }
165                                 }
166                                 children = value;
167                         }
168                 }
169
170                 public string Tag {
171                         get {
172                                 return tag;
173                         }
174                         set {
175                                 if (value == null)
176                                         throw new ArgumentNullException ("Tag");
177                                 if (!IsValidTag (value))
178                                         throw new ArgumentException (Locale.GetText ("Invalid XML string") + ": " + value);
179                                 tag = value;
180                         }
181                 }
182
183                 public string Text {
184                         get {
185                                 return text;
186                         }
187
188                         set {
189                                 if (value != null) {
190                                         if (!IsValidText (value))
191                                                 throw new ArgumentException (
192                                                         Locale.GetText ("Invalid XML string")
193                                                         + ": " + value);
194                                 }
195                                 text = Unescape (value);
196                         }
197                 }
198
199                 public void AddAttribute (string name, string value)
200                 {
201                         if (name == null)
202                                 throw new ArgumentNullException ("name");
203                         if (value == null)
204                                 throw new ArgumentNullException ("value");
205                         if (GetAttribute (name) != null)
206                                 throw new ArgumentException (Locale.GetText ("Duplicate attribute : " + name));
207
208                         if (attributes == null)
209                                 attributes = new ArrayList ();
210                         attributes.Add (new SecurityAttribute (name, value));
211                 }
212
213                 public void AddChild (SecurityElement child)
214                 {
215                         if (child == null)
216                                 throw new ArgumentNullException ("child");
217
218                         if (children == null)
219                                 children = new ArrayList ();
220
221                         children.Add (child);
222                 }
223
224                 public string Attribute (string name)
225                 {
226                         if (name == null)
227                                 throw new ArgumentNullException ("name");
228
229                         SecurityAttribute sa = GetAttribute (name);
230                         return ((sa == null) ? null : sa.Value);
231                 }
232
233                 [ComVisible (false)]
234                 public SecurityElement Copy ()
235                 {
236                         return new SecurityElement (this);
237                 }
238
239                 public bool Equal (SecurityElement other)
240                 {
241                         if (other == null)
242                                 return false;
243                                 
244                         if (this == other)
245                                 return true;
246
247                         if (this.text != other.text)
248                                 return false;
249
250                         if (this.tag != other.tag)
251                                 return false;
252
253                         if (this.attributes == null && other.attributes != null && other.attributes.Count != 0)
254                                 return false;
255                                 
256                         if (other.attributes == null && this.attributes != null && this.attributes.Count != 0)
257                                 return false;
258
259                         if (this.attributes != null && other.attributes != null) {
260                                 if (this.attributes.Count != other.attributes.Count) 
261                                         return false;
262                                 foreach (SecurityAttribute sa1 in attributes) {
263                                         SecurityAttribute sa2 = other.GetAttribute (sa1.Name);
264                                         if ((sa2 == null) || (sa1.Value != sa2.Value))
265                                                 return false;
266                                 }
267                         }
268                         
269                         if (this.children == null && other.children != null && other.children.Count != 0)
270                                 return false;
271                                         
272                         if (other.children == null && this.children != null && this.children.Count != 0)
273                                 return false;
274                                 
275                         if (this.children != null && other.children != null) {
276                                 if (this.children.Count != other.children.Count)
277                                         return false;
278                                 for (int i = 0; i < this.children.Count; i++) 
279                                         if (!((SecurityElement) this.children [i]).Equal ((SecurityElement) other.children [i]))
280                                                 return false;
281                         }
282                         
283                         return true;
284                 }
285
286                 public static string Escape (string str)
287                 {
288                         StringBuilder sb;
289
290                         if (str == null)
291                                 return null;
292
293                         if (str.IndexOfAny (invalid_chars) == -1)
294                                 return str;
295
296                         sb = new StringBuilder ();
297                         int len = str.Length;
298                         
299                         for (int i = 0; i < len; i++) {
300                                 char c = str [i];
301
302                                 switch (c) {
303                                 case '<':  sb.Append ("&lt;"); break;
304                                 case '>':  sb.Append ("&gt;"); break;
305                                 case '"':  sb.Append ("&quot;"); break;
306                                 case '\'': sb.Append ("&apos;"); break;
307                                 case '&':  sb.Append ("&amp;"); break;
308                                 default:   sb.Append (c); break;
309                                 }
310                         }
311
312                         return sb.ToString ();
313                 }
314
315                 private static string Unescape (string str)
316                 {
317                         StringBuilder sb;
318
319                         if (str == null)
320                                 return null;
321
322                         sb = new StringBuilder (str);
323                         sb.Replace ("&lt;", "<");
324                         sb.Replace ("&gt;", ">");
325                         sb.Replace ("&amp;", "&");
326                         sb.Replace ("&quot;", "\"");
327                         sb.Replace ("&apos;", "'");
328                         return sb.ToString ();
329                 }
330
331                 public static SecurityElement FromString (string xml)
332                 {
333                         if (xml == null)
334                                 throw new ArgumentNullException ("xml");
335                         if (xml.Length == 0)
336                                 throw new XmlSyntaxException (Locale.GetText ("Empty string."));
337
338                         try {
339                                 SecurityParser sp = new SecurityParser ();
340                                 sp.LoadXml (xml);
341                                 return sp.ToXml ();
342                         } catch (Exception e) {
343                                 string msg = Locale.GetText ("Invalid XML.");
344                                 throw new XmlSyntaxException (msg, e);
345                         }
346                 }
347
348                 public static bool IsValidAttributeName (string name)
349                 {
350                         return name != null && name.IndexOfAny (invalid_attr_name_chars) == -1;
351                 }
352
353                 public static bool IsValidAttributeValue (string value)
354                 {
355                         return value != null && value.IndexOfAny (invalid_attr_value_chars) == -1;
356                 }
357
358                 public static bool IsValidTag (string tag)
359                 {
360                         return tag != null && tag.IndexOfAny (invalid_tag_chars) == -1;
361                 }
362
363                 public static bool IsValidText (string text)
364                 {
365                         return text != null && text.IndexOfAny (invalid_text_chars) == -1;
366                 }
367
368                 public SecurityElement SearchForChildByTag (string tag) 
369                 {
370                         if (tag == null)
371                                 throw new ArgumentNullException ("tag");
372                                 
373                         if (this.children == null)
374                                 return null;
375                                 
376                         for (int i = 0; i < children.Count; i++) {
377                                 SecurityElement elem = (SecurityElement) children [i];
378                                 if (elem.tag == tag)
379                                         return elem;
380                         }
381                         return null;
382                 }
383
384                 public string SearchForTextOfTag (string tag) 
385                 {
386                         if (tag == null)
387                                 throw new ArgumentNullException ("tag");
388                                 
389                         if (this.tag == tag)
390                                 return this.text;
391                                 
392                         if (this.children == null)
393                                 return null;
394                         
395                         for (int i = 0; i < children.Count; i++) {
396                                 string result = ((SecurityElement) children [i]).SearchForTextOfTag (tag);
397                                 if (result != null) 
398                                         return result;
399                         }
400
401                         return null;
402                 }
403                 
404                 public override string ToString ()
405                 {
406                         StringBuilder s = new StringBuilder ();
407                         ToXml (ref s, 0);
408                         return s.ToString ();
409                 }
410                 
411                 private void ToXml (ref StringBuilder s, int level)
412                 {
413                         s.Append ("<");
414                         s.Append (tag);
415                         
416                         if (attributes != null) {
417                                 s.Append (" ");
418                                 for (int i=0; i < attributes.Count; i++) {
419                                         SecurityAttribute sa = (SecurityAttribute) attributes [i];
420                                         s.Append (sa.Name)
421                                          .Append ("=\"")
422                                          .Append (Escape (sa.Value))
423                                          .Append ("\"");
424                                         if (i != attributes.Count - 1)
425                                                 s.Append (Environment.NewLine);
426                                 }
427                         }
428                         
429                         if ((text == null || text == String.Empty) && 
430                             (children == null || children.Count == 0))
431                                 s.Append ("/>").Append (Environment.NewLine);
432                         else {
433                                 s.Append (">").Append (Escape (text));
434                                 if (children != null) {
435                                         s.Append (Environment.NewLine);
436                                         foreach (SecurityElement child in children) {
437                                                 child.ToXml (ref s, level + 1);
438                                         }
439                                 }
440                                 s.Append ("</")
441                                  .Append (tag)
442                                  .Append (">")
443                                  .Append (Environment.NewLine);
444                         }
445                 }
446
447                 internal SecurityAttribute GetAttribute (string name) 
448                 {
449                         if (attributes != null) {
450                                 foreach (SecurityAttribute sa in attributes) {
451                                         if (sa.Name == name)
452                                                 return sa;
453                                 }
454                         }
455                         return null;
456                 }
457         }
458 }