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;
49 private WebConfigurationSettings ()
53 public static void Init ()
55 lock (typeof (WebConfigurationSettings)) {
59 WebDefaultConfig settings = WebDefaultConfig.GetInstance ();
60 Type t = typeof (ConfigurationSettings);
61 MethodInfo changeConfig = t.GetMethod ("ChangeConfigurationSystem",
64 if (changeConfig == null)
65 throw new ConfigurationException ("Cannot find method CCS");
67 object [] args = new object [] {settings};
68 oldConfig = (IConfigurationSystem) changeConfig.Invoke (null, args);
73 public static void Init (HttpContext context)
76 config.Init (context);
79 public static object GetConfig (string sectionName)
81 return config.GetConfig (sectionName);
84 public static object GetConfig (string sectionName, HttpContext context)
86 return config.GetConfig (sectionName, context);
89 public static string MachineConfigPath {
91 lock (typeof (WebConfigurationSettings)) {
92 if (machineConfigPath != null)
93 return machineConfigPath;
98 Type t = oldConfig.GetType ();
99 MethodInfo getMC = t.GetMethod ("GetMachineConfigPath",
103 throw new ConfigurationException ("Cannot find method GMC");
105 machineConfigPath = (string) getMC.Invoke (null, null);
106 return machineConfigPath;
113 // class WebDefaultConfig: read configuration from machine.config file and application
114 // config file if available.
116 class WebDefaultConfig : IConfigurationSystem
118 static WebDefaultConfig instance;
119 Hashtable fileToConfig;
120 HttpContext firstContext;
123 static WebDefaultConfig ()
125 instance = new WebDefaultConfig ();
128 private WebDefaultConfig ()
130 fileToConfig = new Hashtable ();
133 public static WebDefaultConfig GetInstance ()
138 public object GetConfig (string sectionName)
140 HttpContext current = HttpContext.Current;
142 current = firstContext;
143 return GetConfig (sectionName, current);
146 public object GetConfig (string sectionName, HttpContext context)
151 ConfigurationData config = GetConfigFromFileName (context.Request.CurrentExecutionFilePath, context);
155 return config.GetConfig (sectionName, context);
158 ConfigurationData GetConfigFromFileName (string filepath, HttpContext context)
161 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
163 string dir = UrlUtils.GetDirectory (filepath);
164 if (HttpRuntime.AppDomainAppVirtualPath.Length > dir.Length)
165 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
167 ConfigurationData data = (ConfigurationData) fileToConfig [dir];
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;
176 string upper = Path.Combine (realpath, "Web.config");
177 bool isUpper = File.Exists (upper);
184 string tempDir = dir;
185 if (tempDir == HttpRuntime.AppDomainAppVirtualPath ||
186 tempDir + "/" == HttpRuntime.AppDomainAppVirtualPath) {
188 realpath = HttpRuntime.AppDomainAppPath;
191 ConfigurationData parent = GetConfigFromFileName (tempDir, context);
192 if (wcfile == null) {
193 data = new ConfigurationData (parent, null, realpath);
195 fileToConfig [dir] = data;
199 data = new ConfigurationData (parent, wcfile);
201 data.LoadFromFile (wcfile);
202 fileToConfig [dir] = data;
203 RemotingConfiguration.Configure (wcfile);
211 // nothing. We need a context.
214 public void Init (HttpContext context)
223 firstContext = context;
224 ConfigurationData data = new ConfigurationData ();
225 if (!data.LoadFromFile (WebConfigurationSettings.MachineConfigPath))
226 throw new ConfigurationException ("Cannot find " + WebConfigurationSettings.MachineConfigPath);
228 fileToConfig [WebConfigurationSettings.MachineConfigPath] = data;
233 static string GetAppConfigPath ()
235 AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
237 string configFile = currentInfo.ConfigurationFile;
238 if (configFile == null || configFile.Length == 0)
246 class FileWatcherCache
248 Hashtable cacheTable;
251 FileSystemWatcher watcher;
252 ConfigurationData data;
254 public FileWatcherCache (ConfigurationData data)
257 cacheTable = new Hashtable ();
258 this.path = Path.GetDirectoryName (data.FileName);
259 this.filename = Path.GetFileName (data.FileName);
260 if (!Directory.Exists (path))
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;
271 void SetChanged (object o, FileSystemEventArgs args)
274 if (watcher.Filter == "*.config" &&
275 String.Compare (Path.GetFileName (args.FullPath), "web.config", true) != 0)
280 if (args.ChangeType == WatcherChangeTypes.Created)
281 RemotingConfiguration.Configure (args.FullPath);
283 if (args.ChangeType != WatcherChangeTypes.Deleted)
284 data.LoadFromFile (args.FullPath);
288 public object this [string key] {
291 return cacheTable [key];
296 cacheTable [key] = value;
303 watcher.EnableRaisingEvents = false;
316 public readonly string SectionName;
317 public readonly string TypeName;
318 public readonly bool AllowLocation;
319 public readonly AllowDefinition AllowDefinition;
320 public string FileName;
322 public SectionData (string sectionName, string typeName,
323 bool allowLocation, AllowDefinition allowDefinition)
325 SectionName = sectionName;
327 AllowLocation = allowLocation;
328 AllowDefinition = allowDefinition;
332 class ConfigurationData
334 ConfigurationData parent;
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 '$', ',','\\', '*', '\"', '<', '>'
350 static string forbiddenStr = "';', '?', ':', '@', '&', '=', '+', '$', ',', '\\', '*', '\"', '<', '>'";
352 internal FileWatcherCache FileCache {
355 if (fileCache != null)
358 fileCache = new FileWatcherCache (this);
365 internal string FileName {
366 get { return fileName; }
369 internal ConfigurationData Parent {
370 get { return parent; }
373 internal string DirName {
374 get { return dirname; }
375 set { dirname = value; }
378 internal void Reset ()
384 if (locations != null)
388 public ConfigurationData () : this (null, null)
392 public ConfigurationData (ConfigurationData parent, string filename)
394 this.parent = (parent == this) ? null : parent;
395 this.fileName = filename;
396 factories = new Hashtable ();
399 public ConfigurationData (ConfigurationData parent, string filename, string realdir)
401 this.parent = (parent == this) ? null : parent;
402 this.realdir = realdir;
403 if (filename == null) {
404 this.fileName = Path.Combine (realdir, "*.config");
406 this.fileName = filename;
408 factories = new Hashtable ();
411 public bool LoadFromFile (string fileName)
413 this.fileName = fileName;
414 if (fileName == null || !File.Exists (fileName))
417 XmlTextReader reader = null;
420 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
421 reader = new XmlTextReader (fs);
423 ReadConfig (reader, false);
424 } catch (ConfigurationException) {
426 } catch (Exception e) {
427 throw new ConfigurationException ("Error reading " + fileName, e);
436 public void LoadFromReader (XmlTextReader reader, string fakeFileName, bool isLocation)
438 fileName = fakeFileName;
439 MoveToNextElement (reader);
440 ReadConfig (reader, isLocation);
443 object GetHandler (string sectionName)
446 object o = factories [sectionName];
447 if (o == null || o == removedMark) {
449 return parent.GetHandler (sectionName);
454 if (o is IConfigurationSectionHandler)
455 return (IConfigurationSectionHandler) o;
457 o = CreateNewHandler (sectionName, (SectionData) o);
458 factories [sectionName] = o;
463 object CreateNewHandler (string sectionName, SectionData section)
465 Type t = Type.GetType (section.TypeName);
467 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
469 Type iconfig = typeof (IConfigurationSectionHandler);
470 if (!iconfig.IsAssignableFrom (t))
471 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
473 object o = Activator.CreateInstance (t, true);
475 throw new ConfigurationException ("Cannot get instance for " + t);
480 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
482 if (++i >= sectionPath.Length)
485 if (doc.DocumentElement == null)
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);
495 node = node.NextSibling;
501 XmlDocument GetDocumentForSection (string sectionName)
503 ConfigXmlDocument doc = new ConfigXmlDocument ();
507 string [] sectionPath = sectionName.Split ('/');
508 string outerxml = pending [sectionPath [0]] as string;
509 if (outerxml == null)
512 StringReader reader = new StringReader (outerxml);
513 XmlTextReader rd = new XmlTextReader (reader);
515 doc.LoadSingleElement (fileName, rd);
517 return GetInnerDoc (doc, 0, sectionPath);
520 object GetConfigInternal (string sectionName, HttpContext context, bool useLoc)
522 object handler = GetHandler (sectionName);
523 IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
527 object parentConfig = null;
528 if (parent != null) {
530 parentConfig = parent.GetConfig (sectionName, context);
532 parentConfig = parent.GetConfigOptLocation (sectionName, context, false);
535 XmlDocument doc = GetDocumentForSection (sectionName);
536 if (doc == null || doc.DocumentElement == null)
539 return iconf.Create (parentConfig, fileName, doc.DocumentElement);
542 public object GetConfig (string sectionName, HttpContext context)
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;
550 string target = null;
551 for (int i = 0; i < parts.Length; i++) {
555 target = target + "/" + parts [i];
557 if (locations.ContainsKey (target)) {
558 location = locations [target] as Location;
559 } else if (locations.ContainsKey (target + "/*")) {
560 location = locations [target + "/*"] as Location;
564 if (location == null) {
565 location = locations ["*"] as Location;
568 if (location != null && location.Config != null) {
569 object o = location.Config.GetConfigOptLocation (sectionName, context, false);
576 return GetConfigOptLocation (sectionName, context, true);
579 object GetConfigOptLocation (string sectionName, HttpContext context, bool useLoc)
581 object config = this.FileCache [sectionName];
582 if (config == emptyMark)
589 config = GetConfigInternal (sectionName, context, useLoc);
590 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
596 private object LookForFactory (string key)
598 object o = factories [key];
603 return parent.LookForFactory (key);
608 private void InitRead (XmlTextReader reader)
610 reader.MoveToContent ();
611 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
612 ThrowException ("Configuration file does not have a valid root element", reader);
614 if (reader.HasAttributes)
615 ThrowException ("Unrecognized attribute in root element", reader);
617 MoveToNextElement (reader);
620 internal void MoveToNextElement (XmlTextReader reader)
622 while (reader.Read ()) {
623 XmlNodeType ntype = reader.NodeType;
624 if (ntype == XmlNodeType.Element)
627 if (ntype != XmlNodeType.Whitespace &&
628 ntype != XmlNodeType.Comment &&
629 ntype != XmlNodeType.SignificantWhitespace &&
630 ntype != XmlNodeType.EndElement)
631 ThrowException ("Unrecognized element", reader);
635 private void ReadSection (XmlTextReader reader, string sectionName)
638 string nameValue = null;
639 string typeValue = null;
640 string allowLoc = null, allowDef = null;
641 bool allowLocation = true;
642 AllowDefinition allowDefinition = AllowDefinition.Everywhere;
644 while (reader.MoveToNextAttribute ()) {
645 attName = reader.Name;
649 if (attName == "allowLocation") {
650 if (allowLoc != null)
651 ThrowException ("Duplicated allowLocation attribute.", reader);
653 allowLoc = reader.Value;
654 allowLocation = (allowLoc == "true");
655 if (!allowLocation && allowLoc != "false")
656 ThrowException ("Invalid attribute value", reader);
661 if (attName == "allowDefinition") {
662 if (allowDef != null)
663 ThrowException ("Duplicated allowDefinition attribute.", reader);
665 allowDef = reader.Value;
667 allowDefinition = (AllowDefinition) Enum.Parse (
668 typeof (AllowDefinition), allowDef);
670 ThrowException ("Invalid attribute value", reader);
676 if (attName == "type") {
677 if (typeValue != null)
678 ThrowException ("Duplicated type attribute.", reader);
679 typeValue = reader.Value;
683 if (attName == "name") {
684 if (nameValue != null)
685 ThrowException ("Duplicated name attribute.", reader);
687 nameValue = reader.Value;
688 if (nameValue == "location")
689 ThrowException ("location is a reserved section name", reader);
693 ThrowException ("Unrecognized attribute.", reader);
696 if (nameValue == null || typeValue == null)
697 ThrowException ("Required attribute missing", reader);
699 if (sectionName != null)
700 nameValue = sectionName + '/' + nameValue;
702 reader.MoveToElement();
703 object o = LookForFactory (nameValue);
704 if (o != null && o != removedMark)
705 ThrowException ("Already have a factory for " + nameValue, reader);
707 SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
708 section.FileName = fileName;
709 factories [nameValue] = section;
710 MoveToNextElement (reader);
713 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
715 if (!reader.MoveToNextAttribute () || reader.Name != "name")
716 ThrowException ("Unrecognized attribute.", reader);
718 string removeValue = reader.Value;
719 if (removeValue == null || removeValue.Length == 0)
720 ThrowException ("Empty name to remove", reader);
722 reader.MoveToElement ();
724 if (sectionName != null)
725 removeValue = sectionName + '/' + removeValue;
727 object o = LookForFactory (removeValue);
728 if (o != null && o == removedMark)
729 ThrowException ("No factory for " + removeValue, reader);
731 factories [removeValue] = removedMark;
732 MoveToNextElement (reader);
735 private void ReadSectionGroup (XmlTextReader reader, string configSection)
737 if (!reader.MoveToNextAttribute ())
738 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
740 if (reader.Name != "name")
741 ThrowException ("Unrecognized attribute.", reader);
743 if (reader.MoveToNextAttribute ())
744 ThrowException ("Unrecognized attribute.", reader);
746 string value = reader.Value;
747 if (value == "location")
748 ThrowException ("location is a reserved section name", reader);
750 if (configSection != null)
751 value = configSection + '/' + value;
753 object o = LookForFactory (value);
754 if (o != null && o != removedMark && o != groupMark)
755 ThrowException ("Already have a factory for " + value, reader);
757 factories [value] = groupMark;
758 MoveToNextElement (reader);
759 ReadSections (reader, value);
762 private void ReadSections (XmlTextReader reader, string configSection)
764 int depth = reader.Depth;
765 while (reader.Depth == depth) {
766 string name = reader.Name;
767 if (name == "section") {
768 ReadSection (reader, configSection);
772 if (name == "remove") {
773 ReadRemoveSection (reader, configSection);
777 if (name == "clear") {
778 if (reader.HasAttributes)
779 ThrowException ("Unrecognized attribute.", reader);
782 MoveToNextElement (reader);
786 if (name == "sectionGroup") {
787 ReadSectionGroup (reader, configSection);
791 ThrowException ("Unrecognized element: " + reader.Name, reader);
795 void StoreLocation (string name, XmlTextReader reader)
798 bool haveAllow = false;
799 bool allowOverride = true;
802 while (reader.MoveToNextAttribute ()) {
807 ThrowException ("Duplicate path attribute", reader);
810 if (path.StartsWith ("."))
811 ThrowException ("Path cannot begin with '.'", reader);
813 if (path.IndexOfAny (forbiddenPathChars) != -1)
814 ThrowException ("Path cannot contain " + forbiddenStr, reader);
819 if (att == "allowOverride") {
821 ThrowException ("Duplicate allowOverride attribute", reader);
824 allowOverride = (reader.Value == "true");
825 if (!allowOverride && reader.Value != "false")
826 ThrowException ("allowOverride must be either true or false", reader);
830 ThrowException ("Unrecognized attribute.", reader);
834 return; // empty location tag
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);
842 reader.MoveToElement ();
843 loc.LoadFromString (reader.ReadInnerXml ());
844 locations [loc.Path] = loc;
845 if (!loc.AllowOverride) {
846 XmlTextReader inner = loc.GetReader ();
848 MoveToNextElement (inner);
849 ReadConfig (loc.GetReader (), true);
856 void StorePending (string name, XmlTextReader reader)
859 pending = new Hashtable ();
861 if (pending.ContainsKey (name))
862 ThrowException ("Sections can only appear once: " + name, reader);
864 pending [name] = reader.ReadOuterXml ();
867 void ReadConfig (XmlTextReader reader, bool isLocation)
869 int depth = reader.Depth;
870 while (!reader.EOF && reader.Depth == depth) {
871 string name = reader.Name;
873 if (name == "configSections") {
875 ThrowException ("<configSections> inside <location>", reader);
877 if (reader.HasAttributes)
878 ThrowException ("Unrecognized attribute in <configSections>.", reader);
880 MoveToNextElement (reader);
881 if (reader.Depth > depth)
882 ReadSections (reader, null);
883 } else if (name == "location") {
885 ThrowException ("<location> inside <location>", reader);
887 StoreLocation (name, reader);
888 MoveToNextElement (reader);
889 } else if (name != null && name != ""){
890 StorePending (name, reader);
891 MoveToNextElement (reader);
893 MoveToNextElement (reader);
898 void ThrowException (string text, XmlTextReader reader)
900 throw new ConfigurationException (text, fileName, reader.LineNumber);
908 ConfigurationData parent;
909 ConfigurationData thisOne;
912 public Location (ConfigurationData parent, string path, bool allowOverride)
914 this.parent = parent;
915 this.allowOverride = allowOverride;
916 this.path = (path == null || path == "") ? "*" : path;
919 public bool AllowOverride {
920 get { return (path != "*" || allowOverride); }
927 public string XmlStr {
928 set { xmlstr = value; }
931 public void LoadFromString (string str)
934 throw new ArgumentNullException ("str");
937 throw new InvalidOperationException ();
939 this.xmlstr = str.Trim ();
943 XmlTextReader reader = new XmlTextReader (new StringReader (str));
944 thisOne = new ConfigurationData (parent, parent.FileName);
945 thisOne.LoadFromReader (reader, parent.FileName, true);
948 public XmlTextReader GetReader ()
953 XmlTextReader reader = new XmlTextReader (new StringReader (xmlstr));
957 public ConfigurationData Config {
958 get { return thisOne; }