2008-05-19 George Giolfan <georgegiolfan@yahoo.com>
[mono.git] / mcs / class / System.Web / System.Web.Configuration / WebConfigurationSettings.cs
1 //
2 // System.Configuration.WebConfigurationSettings.cs
3 //
4 // Authors:
5 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (c) 2003,2004 Novell, Inc. (http://www.novell.com)
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Configuration;
33 using System.Collections;
34 using System.IO;
35 using System.Reflection;
36 using System.Web.Util;
37 using System.Xml;
38 #if TARGET_J2EE
39 using vmw.@internal.io;
40 using vmw.common;
41 using System.Web.J2EE;
42 #endif
43
44 namespace System.Web.Configuration
45 {
46         class WebConfigurationSettings
47         {
48 #if TARGET_J2EE
49                 static private IConfigurationSystem oldConfig {
50                         get {
51                                 return (IConfigurationSystem)AppDomain.CurrentDomain.GetData("WebConfigurationSettings.oldConfig");
52                         }
53                         set {
54                                 AppDomain.CurrentDomain.SetData("WebConfigurationSettings.oldConfig", value);
55                         }
56                 }
57
58                 static private WebDefaultConfig config {
59                         get {
60                                 return (WebDefaultConfig)AppDomain.CurrentDomain.GetData("WebConfigurationSettings.config");
61                         }
62                         set {
63                                 AppDomain.CurrentDomain.SetData("WebConfigurationSettings.config", value);
64                         }
65                 }
66 #else
67                 static IConfigurationSystem oldConfig;
68                 static WebDefaultConfig config;
69 #endif
70                 static string machineConfigPath;
71                 const BindingFlags privStatic = BindingFlags.NonPublic | BindingFlags.Static;
72                 static readonly object lockobj = new object ();
73                 
74                 private WebConfigurationSettings ()
75                 {
76                 }
77
78                 public static void Init ()
79                 {
80                         lock (lockobj) {
81                                 if (config != null)
82                                         return;
83
84                                 WebDefaultConfig settings = WebDefaultConfig.GetInstance ();
85                                 Type t = typeof (ConfigurationSettings);
86                                 MethodInfo changeConfig = t.GetMethod ("ChangeConfigurationSystem",
87                                                                       privStatic);
88
89                                 if (changeConfig == null)
90                                         throw new ConfigurationException ("Cannot find method CCS");
91
92                                 object [] args = new object [] {settings};
93                                 oldConfig = (IConfigurationSystem) changeConfig.Invoke (null, args);
94                                 config = settings;
95                         }
96                 }
97
98                 public static void Init (HttpContext context)
99                 {
100                         Init ();
101                         config.Init (context);
102                 }
103                 
104                 public static object GetConfig (string sectionName)
105                 {
106                         return config.GetConfig (sectionName);
107                 }
108
109                 public static object GetConfig (string sectionName, HttpContext context)
110                 {
111                         return config.GetConfig (sectionName, context);
112                 }
113
114                 public static string MachineConfigPath {
115                         get {
116                                 lock (lockobj) {
117                                         if (machineConfigPath != null)
118                                                 return machineConfigPath;
119
120                                         if (config == null)
121                                                 Init ();
122
123                                         Type t = oldConfig.GetType ();
124                                         MethodInfo getMC = t.GetMethod ("GetMachineConfigPath",
125                                                                         privStatic);
126
127                                         if (getMC == null)
128                                                 throw new ConfigurationException ("Cannot find method GMC");
129
130                                         machineConfigPath = (string) getMC.Invoke (null, null);
131                                         return machineConfigPath;
132                                 }
133                         }
134                 }
135         }
136
137         //
138         // class WebDefaultConfig: read configuration from machine.config file and application
139         // config file if available.
140         //
141         class WebDefaultConfig : IConfigurationSystem
142         {
143                 object this_lock = new object ();
144                 
145 #if TARGET_J2EE
146                 static private WebDefaultConfig instance {
147                         get {
148                                 WebDefaultConfig val = (WebDefaultConfig)AppDomain.CurrentDomain.GetData("WebDefaultConfig.instance");
149                                 if (val == null) {
150                                         val = new WebDefaultConfig();
151                                         AppDomain.CurrentDomain.SetData("WebDefaultConfig.instance", val);
152                                 }
153                                 return val;
154                         }
155                         set {
156                                 AppDomain.CurrentDomain.SetData("WebDefaultConfig.instance", value);
157                         }
158                 }
159 #else
160                 static WebDefaultConfig instance;
161 #endif
162                 Hashtable fileToConfig;
163                 HttpContext firstContext;
164                 bool initCalled;
165
166                 static WebDefaultConfig ()
167                 {
168                         instance = new WebDefaultConfig ();
169                 }
170
171                 private WebDefaultConfig ()
172                 {
173                         fileToConfig = new Hashtable ();
174                 }
175
176                 public static WebDefaultConfig GetInstance ()
177                 {
178                         return instance;
179                 }
180
181                 public object GetConfig (string sectionName)
182                 {
183                         HttpContext current = HttpContext.Current;
184                         if (current == null)
185                                 current = firstContext;
186                         return GetConfig (sectionName, current);
187                 }
188
189                 public object GetConfig (string sectionName, HttpContext context)
190                 {
191                         if (context == null)
192                                 return null;
193
194                         ConfigurationData config = GetConfigFromFileName (context.Request.CurrentExecutionFilePath, context);
195                         if (config == null)
196                                 return null;
197
198                         return config.GetConfig (sectionName, context);
199                 }
200
201                 ConfigurationData GetConfigFromFileName (string filepath, HttpContext context)
202                 {
203                         if (filepath == "")
204                                 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
205
206                         string dir = UrlUtils.GetDirectory (filepath);
207                         if (HttpRuntime.AppDomainAppVirtualPath.Length > dir.Length)
208                                 return  (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
209
210                         ConfigurationData data = (ConfigurationData) fileToConfig [dir];
211                         if (data != null)
212                                 return data;
213
214                         string realpath = null;
215                         try {
216                                 realpath = context.Request.MapPath (dir);
217                         } catch {
218                                 realpath = context.Request.MapPath (HttpRuntime.AppDomainAppVirtualPath);
219                         }
220                         
221                         if (realpath == null || realpath.Length == 0)
222                                 realpath = HttpRuntime.AppDomainAppPath;
223                         
224                         string lower = Path.Combine (realpath, "web.config");
225                         bool isLower = File.Exists (lower);
226                         string wcfile = null;
227                         if (!isLower) {
228                                 string upper = Path.Combine (realpath, "Web.config");
229                                 bool isUpper = File.Exists (upper);
230                                 if (isUpper)
231                                         wcfile = upper;
232                         } else {
233                                 wcfile = lower;
234                         }
235
236                         string tempDir = dir;
237                         if (tempDir.Length > 1)
238                                 tempDir = tempDir.Substring (0, tempDir.Length - 1);
239                         if (tempDir == HttpRuntime.AppDomainAppVirtualPath) {
240                                 tempDir = "";
241                                 realpath = HttpRuntime.AppDomainAppPath;
242                         }
243                         ConfigurationData parent = GetConfigFromFileName (tempDir, context);
244                         if (wcfile == null) {
245                                 data = new ConfigurationData (parent, null, realpath);
246                                 data.DirName = dir;
247                                 fileToConfig [dir] = data;
248                         }
249
250                         if (data == null) {
251                                 data = new ConfigurationData (parent, wcfile);
252                                 data.DirName = dir;
253                                 data.LoadFromFile (wcfile);
254                                 fileToConfig [dir] = data;
255                         }
256
257                         return data;
258                 }
259
260                 public void Init ()
261                 {
262                         // nothing. We need a context.
263                 }
264
265                 public void Init (HttpContext context)
266                 {
267                         if (initCalled)
268                                 return;
269
270                         lock (this_lock) {
271                                 if (initCalled)
272                                         return;
273
274                                 firstContext = context;
275                                 ConfigurationData data = new ConfigurationData ();
276                                 if (!data.LoadFromFile (WebConfigurationSettings.MachineConfigPath))
277                                         throw new ConfigurationException ("Cannot find " + WebConfigurationSettings.MachineConfigPath);
278
279                                 fileToConfig [WebConfigurationSettings.MachineConfigPath] = data;
280                                 initCalled = true;
281                         }
282                 }
283         }
284
285         class FileWatcherCache
286         {
287                 Hashtable cacheTable;
288                 string path;
289                 string filename;
290 #if !TARGET_JVM // no file watcher support yet in Grasshopper
291                 FileSystemWatcher watcher;
292 #endif
293                 ConfigurationData data;
294
295                 public FileWatcherCache (ConfigurationData data)
296                 {
297                         this.data = data;
298                         cacheTable = new Hashtable ();
299                         this.path = Path.GetDirectoryName (data.FileName);
300                         this.filename = Path.GetFileName (data.FileName);
301                         if (!Directory.Exists (path))
302                                 return;
303
304 #if !TARGET_JVM
305                         watcher = new FileSystemWatcher (this.path, this.filename);
306                         watcher.NotifyFilter |= NotifyFilters.Size;
307                         
308                         FileSystemEventHandler handler = new FileSystemEventHandler (SetChanged);
309                         watcher.Created += handler;
310                         watcher.Changed += handler;
311                         watcher.Deleted += handler;
312                         watcher.EnableRaisingEvents = true;
313 #endif
314                 }
315
316 #if !TARGET_JVM
317                 void SetChanged (object o, FileSystemEventArgs args)
318                 {
319                         lock (data) {
320                                 cacheTable.Clear ();
321                                 data.Reset ();
322
323                                 if (args.ChangeType != WatcherChangeTypes.Deleted)
324                                         data.LoadFromFile (args.FullPath);
325                         }
326                 }
327 #endif
328                 
329                 public object this [string key] {
330                         get {
331                                 lock (data)
332                                         return cacheTable [key];
333                         }
334
335                         set {
336                                 lock (data)
337                                         cacheTable [key] = value;
338                         }
339                 }
340
341                 public void Close ()
342                 {
343 #if !TARGET_JVM
344                         if (watcher != null)
345                                 watcher.EnableRaisingEvents = false;
346 #endif
347                 }
348         }
349
350         enum AllowDefinition
351         {
352                 Everywhere,
353                 MachineOnly,
354                 MachineToApplication
355         }
356         
357         class SectionData
358         {
359                 public readonly string SectionName;
360                 public readonly string TypeName;
361                 public readonly bool AllowLocation;
362                 public readonly AllowDefinition AllowDefinition;
363                 public string FileName;
364
365                 public SectionData (string sectionName, string typeName,
366                                     bool allowLocation, AllowDefinition allowDefinition)
367                 {
368                         SectionName = sectionName;
369                         TypeName = typeName;
370                         AllowLocation = allowLocation;
371                         AllowDefinition = allowDefinition;
372                 }
373         }
374
375         class ConfigurationData
376         {
377                 object this_lock = new object ();
378                 ConfigurationData parent;
379                 Hashtable factories;
380                 Hashtable pending;
381                 Hashtable locations;
382                 string fileName;
383                 string dirname;
384                 static object removedMark = new object ();
385                 static object groupMark = new object ();
386                 static object emptyMark = new object ();
387                 FileWatcherCache fileCache;
388                 static char [] forbiddenPathChars = new char [] {
389                                         ';', '?', ':', '@', '&', '=', '+',
390                                         '$', ',','\\', '*', '\"', '<', '>'
391                                         };
392
393                 static string forbiddenStr = "';', '?', ':', '@', '&', '=', '+', '$', ',', '\\', '*', '\"', '<', '>'";
394
395                 internal FileWatcherCache FileCache {
396                         get {
397                                 lock (this_lock) {
398                                         if (fileCache != null)
399                                                 return fileCache;
400
401                                         fileCache = new FileWatcherCache (this);
402                                 }
403
404                                 return fileCache;
405                         }
406                 }
407
408                 internal string FileName {
409                         get { return fileName; }
410                 }
411
412                 internal ConfigurationData Parent {
413                         get { return parent; }
414                 }
415
416                 internal string DirName {
417                         get { return dirname; }
418                         set { dirname = value; }
419                 }
420
421                 internal void Reset ()
422                 {
423                         factories.Clear ();
424                         if (pending != null)
425                                 pending.Clear ();
426
427                         if (locations != null)
428                                 locations.Clear ();
429                 }
430                 
431                 public ConfigurationData () : this (null, null)
432                 {
433                 }
434
435                 public ConfigurationData (ConfigurationData parent, string filename)
436                 {
437                         this.parent = (parent == this) ? null : parent;
438                         this.fileName = filename;
439                         factories = new Hashtable ();
440                 }
441
442                 public ConfigurationData (ConfigurationData parent, string filename, string realdir)
443                 {
444                         this.parent = (parent == this) ? null : parent;
445                         if (filename == null) {
446                                 this.fileName = Path.Combine (realdir, "*.config");
447                         } else {
448                                 this.fileName = filename;
449                         }
450                         factories = new Hashtable ();
451                 }
452
453                 public bool LoadFromFile (string fileName)
454                 {
455                         this.fileName = fileName;
456                         Stream fs = null;
457                         if (fileName == null || !File.Exists (fileName)) {
458 #if TARGET_J2EE
459                                 if (fileName != null && fileName.EndsWith("machine.config"))
460                                 {
461                                         if (fileName.StartsWith("/"))
462                                                 fileName = fileName.Substring(1);
463                                         java.lang.ClassLoader cl = (java.lang.ClassLoader)AppDomain.CurrentDomain.GetData("GH_ContextClassLoader");
464                                         if (cl == null)
465                                                 return false;
466                                         java.io.InputStream inputStream = cl.getResourceAsStream(fileName);
467                                         fs = (Stream)IOUtils.getStream(inputStream);
468                                 }
469                                 else
470 #endif
471                                         return false;
472                         }
473
474                         XmlTextReader reader = null;
475
476                         try {
477                                 if (fs == null)
478                                         fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
479                                 reader = new XmlTextReader (fs);
480                                 InitRead (reader);
481                                 ReadConfig (reader, false);
482                         } catch (ConfigurationException) {
483                                 throw;
484                         } catch (Exception e) {
485                                 throw new ConfigurationException ("Error reading " + fileName, e);
486                         } finally {
487                                 if (reader != null)
488                                         reader.Close();
489                         }
490
491                         return true;
492                 }
493
494                 public void LoadFromReader (XmlTextReader reader, string fakeFileName, bool isLocation)
495                 {
496                         fileName = fakeFileName;
497                         MoveToNextElement (reader);
498                         ReadConfig (reader, isLocation);
499                 }
500
501                 object GetHandler (string sectionName)
502                 {
503                         lock (factories) {
504                                 object o = factories [sectionName];
505                                 if (o == null || o == removedMark) {
506                                         if (parent != null)
507                                                 return parent.GetHandler (sectionName);
508
509                                         return null;
510                                 }
511
512                                 if (o is IConfigurationSectionHandler)
513                                         return (IConfigurationSectionHandler) o;
514
515                                 o = CreateNewHandler (sectionName, (SectionData) o);
516                                 factories [sectionName] = o;
517                                 return o;
518                         }
519                 }
520
521                 object CreateNewHandler (string sectionName, SectionData section)
522                 {
523                         Type t = Type.GetType (section.TypeName);
524                         if (t == null)
525                                 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
526
527                         Type iconfig = typeof (IConfigurationSectionHandler);
528                         if (!iconfig.IsAssignableFrom (t))
529                                 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
530                         
531                         object o = Activator.CreateInstance (t, true);
532                         if (o == null)
533                                 throw new ConfigurationException ("Cannot get instance for " + t);
534
535                         return o;
536                 }
537
538                 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
539                 {
540                         if (++i >= sectionPath.Length)
541                                 return doc;
542
543                         if (doc.DocumentElement == null)
544                                 return null;
545
546                         XmlNode node = doc.DocumentElement.FirstChild;
547                         while (node != null) {
548                                 if (node.Name == sectionPath [i]) {
549                                         ConfigXmlDocument result = new ConfigXmlDocument ();
550                                         result.Load (new StringReader (node.OuterXml));
551                                         return GetInnerDoc (result, i, sectionPath);
552                                 }
553                                 node = node.NextSibling;
554                         }
555
556                         return null;
557                 }
558
559                 XmlDocument GetDocumentForSection (string sectionName)
560                 {
561                         ConfigXmlDocument doc = new ConfigXmlDocument ();
562                         if (pending == null)
563                                 return doc;
564
565                         string [] sectionPath = sectionName.Split ('/');
566                         string outerxml = pending [sectionPath [0]] as string;
567                         if (outerxml == null)
568                                 return doc;
569                         
570                         StringReader reader = new StringReader (outerxml);
571                         XmlTextReader rd = new XmlTextReader (reader);
572                         rd.MoveToContent ();
573                         doc.LoadSingleElement (fileName, rd);
574
575                         return GetInnerDoc (doc, 0, sectionPath);
576                 }
577                 
578                 object GetConfigInternal (string sectionName, HttpContext context, bool useLoc)
579                 {
580                         object handler = GetHandler (sectionName);
581                         IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
582                         if (iconf == null)
583                                 return handler;
584
585                         object parentConfig = null;
586                         if (parent != null) {
587                                 if (useLoc)
588                                         parentConfig = parent.GetConfig (sectionName, context);
589                                 else
590                                         parentConfig = parent.GetConfigOptLocation (sectionName, context, false);
591                         }
592
593                         XmlDocument doc = GetDocumentForSection (sectionName);
594                         if (doc == null || doc.DocumentElement == null)
595                                 return parentConfig;
596
597                         return iconf.Create (parentConfig, fileName, doc.DocumentElement);
598                 }
599
600                 string MakeRelative (string fullUrl, string relativeTo)
601                 {
602                         if (fullUrl == relativeTo)
603                                 return String.Empty;
604
605                         if (fullUrl.IndexOf (relativeTo) != 0)
606                                 return null;
607
608                         string leftOver = fullUrl.Substring (relativeTo.Length);
609                         if (leftOver.Length > 0 && leftOver [0] == '/')
610                                 leftOver = leftOver.Substring (1);
611
612                         leftOver = UrlUtils.Canonic (leftOver); 
613                         if (leftOver.Length > 0 && leftOver [0] == '/')
614                                 leftOver = leftOver.Substring (1);
615
616                         return leftOver;
617                 }
618                 
619                                             
620                 public object GetConfig (string sectionName, HttpContext context)
621                 {
622                         if (locations != null && dirname != null) {
623                                 string reduced = MakeRelative (context.Request.CurrentExecutionFilePath, dirname);
624                                 string [] parts = reduced.Split ('/');
625                                 Location location = null;
626
627                                 string target = null;
628                                 for (int i = 0; i < parts.Length; i++) {
629                                         if (target == null)
630                                                 target = parts [i];
631                                         else
632                                                 target = target + "/" + parts [i];
633
634                                         if (locations.ContainsKey (target)) {
635                                                 location = locations [target] as Location;
636                                         } else if (locations.ContainsKey (target + "/*")) {
637                                                 location = locations [target + "/*"] as Location;
638                                         }
639                                 }
640                                 
641                                 if (location == null) {
642                                         location = locations ["*"] as Location;
643                                 }
644
645                                 if (location != null && location.Config != null) {
646                                         object o = location.Config.GetConfigOptLocation (sectionName, context, false);
647                                         if (o != null) {
648                                                 return o;
649                                         }
650                                 }
651                         }
652
653                         return GetConfigOptLocation (sectionName, context, true);
654                 }
655
656                 object GetConfigOptLocation (string sectionName, HttpContext context, bool useLoc)
657                 {
658                         object config = this.FileCache [sectionName];
659                         if (config == emptyMark)
660                                 return null;
661
662                         if (config != null)
663                                 return config;
664
665                         lock (this_lock) {
666                                 config = GetConfigInternal (sectionName, context, useLoc);
667                                 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
668                         }
669
670                         return config;
671                 }
672
673                 private object LookForFactory (string key)
674                 {
675                         object o = factories [key];
676                         if (o != null)
677                                 return o;
678
679                         if (parent != null)
680                                 return parent.LookForFactory (key);
681
682                         return null;
683                 }
684                 
685                 private void InitRead (XmlTextReader reader)
686                 {
687                         reader.MoveToContent ();
688                         if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
689                                 ThrowException ("Configuration file does not have a valid root element", reader);
690
691                         if (reader.HasAttributes)
692                                 ThrowException ("Unrecognized attribute in root element", reader);
693
694                         MoveToNextElement (reader);
695                 }
696
697                 internal void MoveToNextElement (XmlTextReader reader)
698                 {
699                         while (reader.Read ()) {
700                                 XmlNodeType ntype = reader.NodeType;
701                                 if (ntype == XmlNodeType.Element)
702                                         return;
703
704                                 if (ntype != XmlNodeType.Whitespace &&
705                                     ntype != XmlNodeType.Comment &&
706                                     ntype != XmlNodeType.SignificantWhitespace &&
707                                     ntype != XmlNodeType.EndElement)
708                                         ThrowException ("Unrecognized element", reader);
709                         }
710                 }
711
712                 private void ReadSection (XmlTextReader reader, string sectionName)
713                 {
714                         string attName;
715                         string nameValue = null;
716                         string typeValue = null;
717                         string allowLoc = null, allowDef = null;
718                         bool allowLocation = true;
719                         AllowDefinition allowDefinition = AllowDefinition.Everywhere;
720
721                         while (reader.MoveToNextAttribute ()) {
722                                 attName = reader.Name;
723                                 if (attName == null)
724                                         continue;
725
726                                 if (attName == "allowLocation") {
727                                         if (allowLoc != null)
728                                                 ThrowException ("Duplicated allowLocation attribute.", reader);
729
730                                         allowLoc = reader.Value;
731                                         allowLocation = (allowLoc == "true");
732                                         if (!allowLocation && allowLoc != "false")
733                                                 ThrowException ("Invalid attribute value", reader);
734
735                                         continue;
736                                 }
737
738                                 if (attName == "allowDefinition") {
739                                         if (allowDef != null)
740                                                 ThrowException ("Duplicated allowDefinition attribute.", reader);
741
742                                         allowDef = reader.Value;
743                                         try {
744                                                 allowDefinition = (AllowDefinition) Enum.Parse (
745                                                                    typeof (AllowDefinition), allowDef);
746                                         } catch {
747                                                 ThrowException ("Invalid attribute value", reader);
748                                         }
749
750                                         continue;
751                                 }
752
753                                 if (attName == "type")  {
754                                         if (typeValue != null)
755                                                 ThrowException ("Duplicated type attribute.", reader);
756                                         typeValue = reader.Value;
757                                         continue;
758                                 }
759                                 
760                                 if (attName == "name")  {
761                                         if (nameValue != null)
762                                                 ThrowException ("Duplicated name attribute.", reader);
763
764                                         nameValue = reader.Value;
765                                         if (nameValue == "location")
766                                                 ThrowException ("location is a reserved section name", reader);
767                                         continue;
768                                 }
769
770                                 ThrowException ("Unrecognized attribute.", reader);
771                         }
772
773                         if (nameValue == null || typeValue == null)
774                                 ThrowException ("Required attribute missing", reader);
775
776                         if (sectionName != null)
777                                 nameValue = sectionName + '/' + nameValue;
778
779                         reader.MoveToElement();
780                         object o = LookForFactory (nameValue);
781                         if (o != null && o != removedMark)
782                                 ThrowException ("Already have a factory for " + nameValue, reader);
783
784                         SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
785                         section.FileName = fileName;
786                         factories [nameValue] = section;
787                         MoveToNextElement (reader);
788                 }
789
790                 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
791                 {
792                         if (!reader.MoveToNextAttribute () || reader.Name != "name")
793                                 ThrowException ("Unrecognized attribute.", reader);
794
795                         string removeValue = reader.Value;
796                         if (removeValue == null || removeValue.Length == 0)
797                                 ThrowException ("Empty name to remove", reader);
798
799                         reader.MoveToElement ();
800
801                         if (sectionName != null)
802                                 removeValue = sectionName + '/' + removeValue;
803
804                         object o = LookForFactory (removeValue);
805                         if (o != null && o == removedMark)
806                                 ThrowException ("No factory for " + removeValue, reader);
807
808                         factories [removeValue] = removedMark;
809                         MoveToNextElement (reader);
810                 }
811
812                 private void ReadSectionGroup (XmlTextReader reader, string configSection)
813                 {
814                         if (!reader.MoveToNextAttribute ())
815                                 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
816
817                         string value = null;
818 #if NET_2_0
819                         do {
820                                 if (reader.Name == "name") {
821                                         if (value != null)
822                                                 ThrowException ("Duplicate 'name' attribute.", reader);
823                                         value = reader.Value;
824                                 }
825                                 else if (reader.Name != "type")
826                                         ThrowException ("Unrecognized attribute.", reader);
827                         } while (reader.MoveToNextAttribute ());
828 #else
829                         if (reader.Name != "name")
830                                 ThrowException ("Unrecognized attribute.", reader);
831
832                         if (reader.MoveToNextAttribute ())
833                                 ThrowException ("Unrecognized attribute.", reader);
834
835                         value = reader.Value;
836 #endif
837                         if (value == null)
838                                 ThrowException ("No 'name' attribute.", reader);
839
840                         if (value == "location")
841                                 ThrowException ("location is a reserved section name", reader);
842                         
843                         if (configSection != null)
844                                 value = configSection + '/' + value;
845
846                         object o = LookForFactory (value);
847                         if (o != null && o != removedMark && o != groupMark)
848                                 ThrowException ("Already have a factory for " + value, reader);
849
850                         factories [value] = groupMark;
851                         MoveToNextElement (reader);
852                         ReadSections (reader, value);
853                 }
854
855                 private void ReadSections (XmlTextReader reader, string configSection)
856                 {
857                         int depth = reader.Depth;
858                         while (reader.Depth == depth) {
859                                 string name = reader.Name;
860                                 if (name == "section") {
861                                         ReadSection (reader, configSection);
862                                         continue;
863                                 } 
864                                 
865                                 if (name == "remove") {
866                                         ReadRemoveSection (reader, configSection);
867                                         continue;
868                                 }
869
870                                 if (name == "clear") {
871                                         if (reader.HasAttributes)
872                                                 ThrowException ("Unrecognized attribute.", reader);
873
874                                         factories.Clear ();
875                                         MoveToNextElement (reader);
876                                         continue;
877                                 }
878
879                                 if (name == "sectionGroup") {
880                                         ReadSectionGroup (reader, configSection);
881                                         continue;
882                                 }
883
884                                 ThrowException ("Unrecognized element: " + reader.Name, reader);
885                         }
886                 }
887
888                 void StoreLocation (string name, XmlTextReader reader)
889                 {
890                         string path = null;
891                         bool haveAllow = false;
892                         bool allowOverride = true;
893                         string att = null;
894
895                         while (reader.MoveToNextAttribute ()) {
896                                 att = reader.Name;
897
898                                 if (att == "path") {
899                                         if (path != null)
900                                                 ThrowException ("Duplicate path attribute", reader);
901
902                                         path = reader.Value;
903                                         if (path.StartsWith ("."))
904                                                 ThrowException ("Path cannot begin with '.'", reader);
905
906                                         if (path.IndexOfAny (forbiddenPathChars) != -1)
907                                                 ThrowException ("Path cannot contain " + forbiddenStr, reader);
908
909                                         continue;
910                                 }
911
912                                 if (att == "allowOverride") {
913                                         if (haveAllow)
914                                                 ThrowException ("Duplicate allowOverride attribute", reader);
915
916                                         haveAllow = true;
917                                         allowOverride = (reader.Value == "true");
918                                         if (!allowOverride && reader.Value != "false")
919                                                 ThrowException ("allowOverride must be either true or false", reader);
920                                         continue;
921                                 }
922
923                                 ThrowException ("Unrecognized attribute.", reader);
924                         }
925
926                         if (att == null)
927                                 return; // empty location tag
928
929                         Location loc = new Location (this, path, allowOverride);
930                         if (locations == null)
931                                 locations = new Hashtable ();
932                         else if (locations.ContainsKey (loc.Path))
933                                 ThrowException ("Duplicated location path: " + loc.Path, reader);
934
935                         reader.MoveToElement ();
936                         loc.LoadFromString (reader.ReadOuterXml ());
937                         locations [loc.Path] = loc;
938                         if (!loc.AllowOverride) {
939                                 XmlTextReader inner = loc.GetReader ();
940                                 if (inner != null) {
941                                         MoveToNextElement (inner);
942                                         ReadConfig (loc.GetReader (), true);
943                                 }
944                         }
945
946                         loc.XmlStr = null;
947                 }
948
949                 void StorePending (string name, XmlTextReader reader)
950                 {
951                         if (pending == null)
952                                 pending = new Hashtable ();
953
954                         if (pending.ContainsKey (name))
955                                 ThrowException ("Sections can only appear once: " + name, reader);
956
957                         pending [name] = reader.ReadOuterXml ();
958                 }
959
960                 void ReadConfig (XmlTextReader reader, bool isLocation)
961                 {
962                         int depth = reader.Depth;
963                         while (!reader.EOF && reader.Depth == depth) {
964                                 string name = reader.Name;
965
966                                 if (name == "configSections") {
967                                         if (isLocation)
968                                                 ThrowException ("<configSections> inside <location>", reader);
969
970                                         if (reader.HasAttributes)
971                                                 ThrowException ("Unrecognized attribute in <configSections>.", reader);
972
973                                         MoveToNextElement (reader);
974                                         if (reader.Depth > depth)
975                                                 ReadSections (reader, null);
976                                 } else if (name == "location") {
977                                         if (isLocation)
978                                                 ThrowException ("<location> inside <location>", reader);
979
980                                         StoreLocation (name, reader);
981                                         MoveToNextElement (reader);
982                                 } else if (name != null && name != ""){
983                                         StorePending (name, reader);
984                                         MoveToNextElement (reader);
985                                 } else {
986                                         MoveToNextElement (reader);
987                                 }
988                         }
989                 }
990                                 
991                 void ThrowException (string text, XmlTextReader reader)
992                 {
993                         throw new ConfigurationException (text, fileName, reader.LineNumber);
994                 }
995         }
996
997         class Location
998         {
999                 string path;
1000                 bool allowOverride;
1001                 ConfigurationData parent;
1002                 ConfigurationData thisOne;
1003                 string xmlstr;
1004
1005                 public Location (ConfigurationData parent, string path, bool allowOverride)
1006                 {
1007                         this.parent = parent;
1008                         this.allowOverride = allowOverride;
1009                         this.path = (path == null || path == "") ? "*" : path;
1010                 }
1011
1012                 public bool AllowOverride {
1013                         get { return (path != "*" || allowOverride); }
1014                 }
1015
1016                 public string Path {
1017                         get { return path; }
1018                 }
1019                 
1020                 public string XmlStr {
1021                         set { xmlstr = value; }
1022                 }
1023                 
1024                 public void LoadFromString (string str)
1025                 {
1026                         if (str == null)
1027                                 throw new ArgumentNullException ("str");
1028
1029                         if (thisOne != null)
1030                                 throw new InvalidOperationException ();
1031
1032                         this.xmlstr = str.Trim ();
1033                         if (xmlstr == "")
1034                                 return;
1035
1036                         XmlTextReader reader = GetReader ();
1037                         thisOne = new ConfigurationData (parent, parent.FileName);
1038                         thisOne.LoadFromReader (reader, parent.FileName, true);
1039                 }
1040
1041                 public XmlTextReader GetReader ()
1042                 {
1043                         if (xmlstr == "")
1044                                 return null;
1045
1046                         XmlTextReader reader = new XmlTextReader (new StringReader (xmlstr));
1047                         reader.ReadStartElement ("location");
1048                         return reader;
1049                 }
1050
1051                 public ConfigurationData Config {
1052                         get { return thisOne; }
1053                 }
1054         }
1055 }
1056
1057