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