2 // System.Configuration.WebConfigurationSettings.cs
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (c) 2003,2004 Novell, Inc. (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System.Configuration;
33 using System.Collections;
35 using System.Reflection;
36 using System.Runtime.Remoting;
37 using System.Web.Util;
40 namespace System.Web.Configuration
42 class WebConfigurationSettings
44 static IConfigurationSystem oldConfig;
45 static WebDefaultConfig config;
46 static string machineConfigPath;
47 const BindingFlags privStatic = BindingFlags.NonPublic | BindingFlags.Static;
48 static readonly object lockobj = new object ();
50 private WebConfigurationSettings ()
54 public static void Init ()
60 WebDefaultConfig settings = WebDefaultConfig.GetInstance ();
61 Type t = typeof (ConfigurationSettings);
62 MethodInfo changeConfig = t.GetMethod ("ChangeConfigurationSystem",
65 if (changeConfig == null)
66 throw new ConfigurationException ("Cannot find method CCS");
68 object [] args = new object [] {settings};
69 oldConfig = (IConfigurationSystem) changeConfig.Invoke (null, args);
74 public static void Init (HttpContext context)
77 config.Init (context);
80 public static object GetConfig (string sectionName)
82 return config.GetConfig (sectionName);
85 public static object GetConfig (string sectionName, HttpContext context)
87 return config.GetConfig (sectionName, context);
90 public static string MachineConfigPath {
93 if (machineConfigPath != null)
94 return machineConfigPath;
99 Type t = oldConfig.GetType ();
100 MethodInfo getMC = t.GetMethod ("GetMachineConfigPath",
104 throw new ConfigurationException ("Cannot find method GMC");
106 machineConfigPath = (string) getMC.Invoke (null, null);
107 return machineConfigPath;
114 // class WebDefaultConfig: read configuration from machine.config file and application
115 // config file if available.
117 class WebDefaultConfig : IConfigurationSystem
119 static WebDefaultConfig instance;
120 Hashtable fileToConfig;
121 HttpContext firstContext;
124 static WebDefaultConfig ()
126 instance = new WebDefaultConfig ();
129 private WebDefaultConfig ()
131 fileToConfig = new Hashtable ();
134 public static WebDefaultConfig GetInstance ()
139 public object GetConfig (string sectionName)
141 HttpContext current = HttpContext.Current;
143 current = firstContext;
144 return GetConfig (sectionName, current);
147 public object GetConfig (string sectionName, HttpContext context)
152 ConfigurationData config = GetConfigFromFileName (context.Request.CurrentExecutionFilePath, context);
156 return config.GetConfig (sectionName, context);
159 ConfigurationData GetConfigFromFileName (string filepath, HttpContext context)
162 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
164 string dir = UrlUtils.GetDirectory (filepath);
165 if (HttpRuntime.AppDomainAppVirtualPath.Length > dir.Length)
166 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
168 ConfigurationData data = (ConfigurationData) fileToConfig [dir];
172 string realpath = context.Request.MapPath (dir);
173 string lower = Path.Combine (realpath, "web.config");
174 bool isLower = File.Exists (lower);
175 string wcfile = null;
177 string upper = Path.Combine (realpath, "Web.config");
178 bool isUpper = File.Exists (upper);
185 string tempDir = dir;
186 if (tempDir == HttpRuntime.AppDomainAppVirtualPath ||
187 tempDir + "/" == HttpRuntime.AppDomainAppVirtualPath) {
189 realpath = HttpRuntime.AppDomainAppPath;
192 ConfigurationData parent = GetConfigFromFileName (tempDir, context);
193 if (wcfile == null) {
194 data = new ConfigurationData (parent, null, realpath);
196 fileToConfig [dir] = data;
200 data = new ConfigurationData (parent, wcfile);
202 data.LoadFromFile (wcfile);
203 fileToConfig [dir] = data;
204 RemotingConfiguration.Configure (wcfile);
212 // nothing. We need a context.
215 public void Init (HttpContext context)
224 firstContext = context;
225 ConfigurationData data = new ConfigurationData ();
226 if (!data.LoadFromFile (WebConfigurationSettings.MachineConfigPath))
227 throw new ConfigurationException ("Cannot find " + WebConfigurationSettings.MachineConfigPath);
229 fileToConfig [WebConfigurationSettings.MachineConfigPath] = data;
235 class FileWatcherCache
237 Hashtable cacheTable;
240 FileSystemWatcher watcher;
241 ConfigurationData data;
243 public FileWatcherCache (ConfigurationData data)
246 cacheTable = new Hashtable ();
247 this.path = Path.GetDirectoryName (data.FileName);
248 this.filename = Path.GetFileName (data.FileName);
249 if (!Directory.Exists (path))
252 watcher = new FileSystemWatcher (this.path, this.filename);
253 FileSystemEventHandler handler = new FileSystemEventHandler (SetChanged);
254 watcher.Created += handler;
255 watcher.Changed += handler;
256 watcher.Deleted += handler;
257 watcher.EnableRaisingEvents = true;
260 void SetChanged (object o, FileSystemEventArgs args)
265 if (args.ChangeType == WatcherChangeTypes.Created)
266 RemotingConfiguration.Configure (args.FullPath);
268 if (args.ChangeType != WatcherChangeTypes.Deleted)
269 data.LoadFromFile (args.FullPath);
273 public object this [string key] {
276 return cacheTable [key];
281 cacheTable [key] = value;
288 watcher.EnableRaisingEvents = false;
301 public readonly string SectionName;
302 public readonly string TypeName;
303 public readonly bool AllowLocation;
304 public readonly AllowDefinition AllowDefinition;
305 public string FileName;
307 public SectionData (string sectionName, string typeName,
308 bool allowLocation, AllowDefinition allowDefinition)
310 SectionName = sectionName;
312 AllowLocation = allowLocation;
313 AllowDefinition = allowDefinition;
317 class ConfigurationData
319 ConfigurationData parent;
325 static object removedMark = new object ();
326 static object groupMark = new object ();
327 static object emptyMark = new object ();
328 FileWatcherCache fileCache;
329 static char [] forbiddenPathChars = new char [] {
330 ';', '?', ':', '@', '&', '=', '+',
331 '$', ',','\\', '*', '\"', '<', '>'
334 static string forbiddenStr = "';', '?', ':', '@', '&', '=', '+', '$', ',', '\\', '*', '\"', '<', '>'";
336 internal FileWatcherCache FileCache {
339 if (fileCache != null)
342 fileCache = new FileWatcherCache (this);
349 internal string FileName {
350 get { return fileName; }
353 internal ConfigurationData Parent {
354 get { return parent; }
357 internal string DirName {
358 get { return dirname; }
359 set { dirname = value; }
362 internal void Reset ()
368 if (locations != null)
372 public ConfigurationData () : this (null, null)
376 public ConfigurationData (ConfigurationData parent, string filename)
378 this.parent = (parent == this) ? null : parent;
379 this.fileName = filename;
380 factories = new Hashtable ();
383 public ConfigurationData (ConfigurationData parent, string filename, string realdir)
385 this.parent = (parent == this) ? null : parent;
386 if (filename == null) {
387 this.fileName = Path.Combine (realdir, "*.config");
389 this.fileName = filename;
391 factories = new Hashtable ();
394 public bool LoadFromFile (string fileName)
396 this.fileName = fileName;
397 if (fileName == null || !File.Exists (fileName))
400 XmlTextReader reader = null;
403 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
404 reader = new XmlTextReader (fs);
406 ReadConfig (reader, false);
407 } catch (ConfigurationException) {
409 } catch (Exception e) {
410 throw new ConfigurationException ("Error reading " + fileName, e);
419 public void LoadFromReader (XmlTextReader reader, string fakeFileName, bool isLocation)
421 fileName = fakeFileName;
422 MoveToNextElement (reader);
423 ReadConfig (reader, isLocation);
426 object GetHandler (string sectionName)
429 object o = factories [sectionName];
430 if (o == null || o == removedMark) {
432 return parent.GetHandler (sectionName);
437 if (o is IConfigurationSectionHandler)
438 return (IConfigurationSectionHandler) o;
440 o = CreateNewHandler (sectionName, (SectionData) o);
441 factories [sectionName] = o;
446 object CreateNewHandler (string sectionName, SectionData section)
448 Type t = Type.GetType (section.TypeName);
450 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
452 Type iconfig = typeof (IConfigurationSectionHandler);
453 if (!iconfig.IsAssignableFrom (t))
454 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
456 object o = Activator.CreateInstance (t, true);
458 throw new ConfigurationException ("Cannot get instance for " + t);
463 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
465 if (++i >= sectionPath.Length)
468 if (doc.DocumentElement == null)
471 XmlNode node = doc.DocumentElement.FirstChild;
472 while (node != null) {
473 if (node.Name == sectionPath [i]) {
474 ConfigXmlDocument result = new ConfigXmlDocument ();
475 result.Load (new StringReader (node.OuterXml));
476 return GetInnerDoc (result, i, sectionPath);
478 node = node.NextSibling;
484 XmlDocument GetDocumentForSection (string sectionName)
486 ConfigXmlDocument doc = new ConfigXmlDocument ();
490 string [] sectionPath = sectionName.Split ('/');
491 string outerxml = pending [sectionPath [0]] as string;
492 if (outerxml == null)
495 StringReader reader = new StringReader (outerxml);
496 XmlTextReader rd = new XmlTextReader (reader);
498 doc.LoadSingleElement (fileName, rd);
500 return GetInnerDoc (doc, 0, sectionPath);
503 object GetConfigInternal (string sectionName, HttpContext context, bool useLoc)
505 object handler = GetHandler (sectionName);
506 IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
510 object parentConfig = null;
511 if (parent != null) {
513 parentConfig = parent.GetConfig (sectionName, context);
515 parentConfig = parent.GetConfigOptLocation (sectionName, context, false);
518 XmlDocument doc = GetDocumentForSection (sectionName);
519 if (doc == null || doc.DocumentElement == null)
522 return iconf.Create (parentConfig, fileName, doc.DocumentElement);
525 public object GetConfig (string sectionName, HttpContext context)
527 if (locations != null && dirname != null) {
528 string reduced = UrlUtils.MakeRelative (context.Request.CurrentExecutionFilePath, dirname);
529 string [] parts = reduced.Split ('/');
530 Location location = null;
532 string target = null;
533 for (int i = 0; i < parts.Length; i++) {
537 target = target + "/" + parts [i];
539 if (locations.ContainsKey (target)) {
540 location = locations [target] as Location;
541 } else if (locations.ContainsKey (target + "/*")) {
542 location = locations [target + "/*"] as Location;
546 if (location == null) {
547 location = locations ["*"] as Location;
550 if (location != null && location.Config != null) {
551 object o = location.Config.GetConfigOptLocation (sectionName, context, false);
558 return GetConfigOptLocation (sectionName, context, true);
561 object GetConfigOptLocation (string sectionName, HttpContext context, bool useLoc)
563 object config = this.FileCache [sectionName];
564 if (config == emptyMark)
571 config = GetConfigInternal (sectionName, context, useLoc);
572 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
578 private object LookForFactory (string key)
580 object o = factories [key];
585 return parent.LookForFactory (key);
590 private void InitRead (XmlTextReader reader)
592 reader.MoveToContent ();
593 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
594 ThrowException ("Configuration file does not have a valid root element", reader);
596 if (reader.HasAttributes)
597 ThrowException ("Unrecognized attribute in root element", reader);
599 MoveToNextElement (reader);
602 internal void MoveToNextElement (XmlTextReader reader)
604 while (reader.Read ()) {
605 XmlNodeType ntype = reader.NodeType;
606 if (ntype == XmlNodeType.Element)
609 if (ntype != XmlNodeType.Whitespace &&
610 ntype != XmlNodeType.Comment &&
611 ntype != XmlNodeType.SignificantWhitespace &&
612 ntype != XmlNodeType.EndElement)
613 ThrowException ("Unrecognized element", reader);
617 private void ReadSection (XmlTextReader reader, string sectionName)
620 string nameValue = null;
621 string typeValue = null;
622 string allowLoc = null, allowDef = null;
623 bool allowLocation = true;
624 AllowDefinition allowDefinition = AllowDefinition.Everywhere;
626 while (reader.MoveToNextAttribute ()) {
627 attName = reader.Name;
631 if (attName == "allowLocation") {
632 if (allowLoc != null)
633 ThrowException ("Duplicated allowLocation attribute.", reader);
635 allowLoc = reader.Value;
636 allowLocation = (allowLoc == "true");
637 if (!allowLocation && allowLoc != "false")
638 ThrowException ("Invalid attribute value", reader);
643 if (attName == "allowDefinition") {
644 if (allowDef != null)
645 ThrowException ("Duplicated allowDefinition attribute.", reader);
647 allowDef = reader.Value;
649 allowDefinition = (AllowDefinition) Enum.Parse (
650 typeof (AllowDefinition), allowDef);
652 ThrowException ("Invalid attribute value", reader);
658 if (attName == "type") {
659 if (typeValue != null)
660 ThrowException ("Duplicated type attribute.", reader);
661 typeValue = reader.Value;
665 if (attName == "name") {
666 if (nameValue != null)
667 ThrowException ("Duplicated name attribute.", reader);
669 nameValue = reader.Value;
670 if (nameValue == "location")
671 ThrowException ("location is a reserved section name", reader);
675 ThrowException ("Unrecognized attribute.", reader);
678 if (nameValue == null || typeValue == null)
679 ThrowException ("Required attribute missing", reader);
681 if (sectionName != null)
682 nameValue = sectionName + '/' + nameValue;
684 reader.MoveToElement();
685 object o = LookForFactory (nameValue);
686 if (o != null && o != removedMark)
687 ThrowException ("Already have a factory for " + nameValue, reader);
689 SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
690 section.FileName = fileName;
691 factories [nameValue] = section;
692 MoveToNextElement (reader);
695 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
697 if (!reader.MoveToNextAttribute () || reader.Name != "name")
698 ThrowException ("Unrecognized attribute.", reader);
700 string removeValue = reader.Value;
701 if (removeValue == null || removeValue.Length == 0)
702 ThrowException ("Empty name to remove", reader);
704 reader.MoveToElement ();
706 if (sectionName != null)
707 removeValue = sectionName + '/' + removeValue;
709 object o = LookForFactory (removeValue);
710 if (o != null && o == removedMark)
711 ThrowException ("No factory for " + removeValue, reader);
713 factories [removeValue] = removedMark;
714 MoveToNextElement (reader);
717 private void ReadSectionGroup (XmlTextReader reader, string configSection)
719 if (!reader.MoveToNextAttribute ())
720 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
722 if (reader.Name != "name")
723 ThrowException ("Unrecognized attribute.", reader);
725 if (reader.MoveToNextAttribute ())
726 ThrowException ("Unrecognized attribute.", reader);
728 string value = reader.Value;
729 if (value == "location")
730 ThrowException ("location is a reserved section name", reader);
732 if (configSection != null)
733 value = configSection + '/' + value;
735 object o = LookForFactory (value);
736 if (o != null && o != removedMark && o != groupMark)
737 ThrowException ("Already have a factory for " + value, reader);
739 factories [value] = groupMark;
740 MoveToNextElement (reader);
741 ReadSections (reader, value);
744 private void ReadSections (XmlTextReader reader, string configSection)
746 int depth = reader.Depth;
747 while (reader.Depth == depth) {
748 string name = reader.Name;
749 if (name == "section") {
750 ReadSection (reader, configSection);
754 if (name == "remove") {
755 ReadRemoveSection (reader, configSection);
759 if (name == "clear") {
760 if (reader.HasAttributes)
761 ThrowException ("Unrecognized attribute.", reader);
764 MoveToNextElement (reader);
768 if (name == "sectionGroup") {
769 ReadSectionGroup (reader, configSection);
773 ThrowException ("Unrecognized element: " + reader.Name, reader);
777 void StoreLocation (string name, XmlTextReader reader)
780 bool haveAllow = false;
781 bool allowOverride = true;
784 while (reader.MoveToNextAttribute ()) {
789 ThrowException ("Duplicate path attribute", reader);
792 if (path.StartsWith ("."))
793 ThrowException ("Path cannot begin with '.'", reader);
795 if (path.IndexOfAny (forbiddenPathChars) != -1)
796 ThrowException ("Path cannot contain " + forbiddenStr, reader);
801 if (att == "allowOverride") {
803 ThrowException ("Duplicate allowOverride attribute", reader);
806 allowOverride = (reader.Value == "true");
807 if (!allowOverride && reader.Value != "false")
808 ThrowException ("allowOverride must be either true or false", reader);
812 ThrowException ("Unrecognized attribute.", reader);
816 return; // empty location tag
818 Location loc = new Location (this, path, allowOverride);
819 if (locations == null)
820 locations = new Hashtable ();
821 else if (locations.ContainsKey (loc.Path))
822 ThrowException ("Duplicated location path: " + loc.Path, reader);
824 reader.MoveToElement ();
825 loc.LoadFromString (reader.ReadInnerXml ());
826 locations [loc.Path] = loc;
827 if (!loc.AllowOverride) {
828 XmlTextReader inner = loc.GetReader ();
830 MoveToNextElement (inner);
831 ReadConfig (loc.GetReader (), true);
838 void StorePending (string name, XmlTextReader reader)
841 pending = new Hashtable ();
843 if (pending.ContainsKey (name))
844 ThrowException ("Sections can only appear once: " + name, reader);
846 pending [name] = reader.ReadOuterXml ();
849 void ReadConfig (XmlTextReader reader, bool isLocation)
851 int depth = reader.Depth;
852 while (!reader.EOF && reader.Depth == depth) {
853 string name = reader.Name;
855 if (name == "configSections") {
857 ThrowException ("<configSections> inside <location>", reader);
859 if (reader.HasAttributes)
860 ThrowException ("Unrecognized attribute in <configSections>.", reader);
862 MoveToNextElement (reader);
863 if (reader.Depth > depth)
864 ReadSections (reader, null);
865 } else if (name == "location") {
867 ThrowException ("<location> inside <location>", reader);
869 StoreLocation (name, reader);
870 MoveToNextElement (reader);
871 } else if (name != null && name != ""){
872 StorePending (name, reader);
873 MoveToNextElement (reader);
875 MoveToNextElement (reader);
880 void ThrowException (string text, XmlTextReader reader)
882 throw new ConfigurationException (text, fileName, reader.LineNumber);
890 ConfigurationData parent;
891 ConfigurationData thisOne;
894 public Location (ConfigurationData parent, string path, bool allowOverride)
896 this.parent = parent;
897 this.allowOverride = allowOverride;
898 this.path = (path == null || path == "") ? "*" : path;
901 public bool AllowOverride {
902 get { return (path != "*" || allowOverride); }
909 public string XmlStr {
910 set { xmlstr = value; }
913 public void LoadFromString (string str)
916 throw new ArgumentNullException ("str");
919 throw new InvalidOperationException ();
921 this.xmlstr = str.Trim ();
925 XmlTextReader reader = new XmlTextReader (new StringReader (str));
926 thisOne = new ConfigurationData (parent, parent.FileName);
927 thisOne.LoadFromReader (reader, parent.FileName, true);
930 public XmlTextReader GetReader ()
935 XmlTextReader reader = new XmlTextReader (new StringReader (xmlstr));
939 public ConfigurationData Config {
940 get { return thisOne; }