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