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.Web.Util;
39 using vmw.@internal.io;
41 using System.Web.J2EE;
44 namespace System.Web.Configuration
46 class WebConfigurationSettings
49 static private IConfigurationSystem oldConfig {
51 return (IConfigurationSystem)AppDomain.CurrentDomain.GetData("WebConfigurationSettings.oldConfig");
54 AppDomain.CurrentDomain.SetData("WebConfigurationSettings.oldConfig", value);
58 static private WebDefaultConfig config {
60 return (WebDefaultConfig)AppDomain.CurrentDomain.GetData("WebConfigurationSettings.config");
63 AppDomain.CurrentDomain.SetData("WebConfigurationSettings.config", value);
67 static IConfigurationSystem oldConfig;
68 static WebDefaultConfig config;
70 static string machineConfigPath;
71 const BindingFlags privStatic = BindingFlags.NonPublic | BindingFlags.Static;
72 static readonly object lockobj = new object ();
74 private WebConfigurationSettings ()
78 public static void Init ()
84 WebDefaultConfig settings = WebDefaultConfig.GetInstance ();
85 Type t = typeof (ConfigurationSettings);
86 MethodInfo changeConfig = t.GetMethod ("ChangeConfigurationSystem",
89 if (changeConfig == null)
90 throw new ConfigurationException ("Cannot find method CCS");
92 object [] args = new object [] {settings};
93 oldConfig = (IConfigurationSystem) changeConfig.Invoke (null, args);
98 public static void Init (HttpContext context)
101 config.Init (context);
104 public static object GetConfig (string sectionName)
106 return config.GetConfig (sectionName);
109 public static object GetConfig (string sectionName, HttpContext context)
111 return config.GetConfig (sectionName, context);
114 public static string MachineConfigPath {
117 if (machineConfigPath != null)
118 return machineConfigPath;
123 Type t = oldConfig.GetType ();
124 MethodInfo getMC = t.GetMethod ("GetMachineConfigPath",
128 throw new ConfigurationException ("Cannot find method GMC");
130 machineConfigPath = (string) getMC.Invoke (null, null);
131 return machineConfigPath;
138 // class WebDefaultConfig: read configuration from machine.config file and application
139 // config file if available.
141 class WebDefaultConfig : IConfigurationSystem
143 object this_lock = new object ();
146 static private WebDefaultConfig instance {
148 WebDefaultConfig val = (WebDefaultConfig)AppDomain.CurrentDomain.GetData("WebDefaultConfig.instance");
150 val = new WebDefaultConfig();
151 AppDomain.CurrentDomain.SetData("WebDefaultConfig.instance", val);
156 AppDomain.CurrentDomain.SetData("WebDefaultConfig.instance", value);
160 static WebDefaultConfig instance;
162 Hashtable fileToConfig;
163 HttpContext firstContext;
166 static WebDefaultConfig ()
168 instance = new WebDefaultConfig ();
171 private WebDefaultConfig ()
173 fileToConfig = new Hashtable ();
176 public static WebDefaultConfig GetInstance ()
181 public object GetConfig (string sectionName)
183 HttpContext current = HttpContext.Current;
185 current = firstContext;
186 return GetConfig (sectionName, current);
189 public object GetConfig (string sectionName, HttpContext context)
194 ConfigurationData config = GetConfigFromFileName (context.Request.CurrentExecutionFilePath, context);
198 return config.GetConfig (sectionName, context);
201 ConfigurationData GetConfigFromFileName (string filepath, HttpContext context)
204 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
206 string dir = UrlUtils.GetDirectory (filepath);
207 if (HttpRuntime.AppDomainAppVirtualPath.Length > dir.Length)
208 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
210 ConfigurationData data = (ConfigurationData) fileToConfig [dir];
214 string realpath = null;
216 realpath = context.Request.MapPath (dir);
218 realpath = context.Request.MapPath (HttpRuntime.AppDomainAppVirtualPath);
221 if (realpath == null || realpath.Length == 0)
222 realpath = HttpRuntime.AppDomainAppPath;
224 string lower = Path.Combine (realpath, "web.config");
225 bool isLower = File.Exists (lower);
226 string wcfile = null;
228 string upper = Path.Combine (realpath, "Web.config");
229 bool isUpper = File.Exists (upper);
236 string tempDir = dir;
237 if (tempDir.Length > 1)
238 tempDir = tempDir.Substring (0, tempDir.Length - 1);
239 if (tempDir == HttpRuntime.AppDomainAppVirtualPath) {
241 realpath = HttpRuntime.AppDomainAppPath;
243 ConfigurationData parent = GetConfigFromFileName (tempDir, context);
244 if (wcfile == null) {
245 data = new ConfigurationData (parent, null, realpath);
247 fileToConfig [dir] = data;
251 data = new ConfigurationData (parent, wcfile);
253 data.LoadFromFile (wcfile);
254 fileToConfig [dir] = data;
262 // nothing. We need a context.
265 public void Init (HttpContext context)
274 firstContext = context;
275 ConfigurationData data = new ConfigurationData ();
276 if (!data.LoadFromFile (WebConfigurationSettings.MachineConfigPath))
277 throw new ConfigurationException ("Cannot find " + WebConfigurationSettings.MachineConfigPath);
279 fileToConfig [WebConfigurationSettings.MachineConfigPath] = data;
285 class FileWatcherCache
287 Hashtable cacheTable;
290 #if !TARGET_JVM // no file watcher support yet in Grasshopper
291 FileSystemWatcher watcher;
293 ConfigurationData data;
295 public FileWatcherCache (ConfigurationData data)
298 cacheTable = new Hashtable ();
299 this.path = Path.GetDirectoryName (data.FileName);
300 this.filename = Path.GetFileName (data.FileName);
301 if (!Directory.Exists (path))
305 watcher = new FileSystemWatcher (this.path, this.filename);
306 watcher.NotifyFilter |= NotifyFilters.Size;
308 FileSystemEventHandler handler = new FileSystemEventHandler (SetChanged);
309 watcher.Created += handler;
310 watcher.Changed += handler;
311 watcher.Deleted += handler;
312 watcher.EnableRaisingEvents = true;
317 void SetChanged (object o, FileSystemEventArgs args)
323 if (args.ChangeType != WatcherChangeTypes.Deleted)
324 data.LoadFromFile (args.FullPath);
329 public object this [string key] {
332 return cacheTable [key];
337 cacheTable [key] = value;
345 watcher.EnableRaisingEvents = false;
359 public readonly string SectionName;
360 public readonly string TypeName;
361 public readonly bool AllowLocation;
362 public readonly AllowDefinition AllowDefinition;
363 public string FileName;
365 public SectionData (string sectionName, string typeName,
366 bool allowLocation, AllowDefinition allowDefinition)
368 SectionName = sectionName;
370 AllowLocation = allowLocation;
371 AllowDefinition = allowDefinition;
375 class ConfigurationData
377 object this_lock = new object ();
378 ConfigurationData parent;
384 static object removedMark = new object ();
385 static object groupMark = new object ();
386 static object emptyMark = new object ();
387 FileWatcherCache fileCache;
388 static char [] forbiddenPathChars = new char [] {
389 ';', '?', ':', '@', '&', '=', '+',
390 '$', ',','\\', '*', '\"', '<', '>'
393 static string forbiddenStr = "';', '?', ':', '@', '&', '=', '+', '$', ',', '\\', '*', '\"', '<', '>'";
395 internal FileWatcherCache FileCache {
398 if (fileCache != null)
401 fileCache = new FileWatcherCache (this);
408 internal string FileName {
409 get { return fileName; }
412 internal ConfigurationData Parent {
413 get { return parent; }
416 internal string DirName {
417 get { return dirname; }
418 set { dirname = value; }
421 internal void Reset ()
427 if (locations != null)
431 public ConfigurationData () : this (null, null)
435 public ConfigurationData (ConfigurationData parent, string filename)
437 this.parent = (parent == this) ? null : parent;
438 this.fileName = filename;
439 factories = new Hashtable ();
442 public ConfigurationData (ConfigurationData parent, string filename, string realdir)
444 this.parent = (parent == this) ? null : parent;
445 if (filename == null) {
446 this.fileName = Path.Combine (realdir, "*.config");
448 this.fileName = filename;
450 factories = new Hashtable ();
453 public bool LoadFromFile (string fileName)
455 this.fileName = fileName;
457 if (fileName == null || !File.Exists (fileName)) {
459 if (fileName != null && fileName.EndsWith("machine.config"))
461 if (fileName.StartsWith("/"))
462 fileName = fileName.Substring(1);
463 java.lang.ClassLoader cl = (java.lang.ClassLoader)AppDomain.CurrentDomain.GetData("GH_ContextClassLoader");
466 java.io.InputStream inputStream = cl.getResourceAsStream(fileName);
467 fs = (Stream)IOUtils.getStream(inputStream);
474 XmlTextReader reader = null;
478 fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
479 reader = new XmlTextReader (fs);
481 ReadConfig (reader, false);
482 } catch (ConfigurationException) {
484 } catch (Exception e) {
485 throw new ConfigurationException ("Error reading " + fileName, e);
494 public void LoadFromReader (XmlTextReader reader, string fakeFileName, bool isLocation)
496 fileName = fakeFileName;
497 MoveToNextElement (reader);
498 ReadConfig (reader, isLocation);
501 object GetHandler (string sectionName)
504 object o = factories [sectionName];
505 if (o == null || o == removedMark) {
507 return parent.GetHandler (sectionName);
512 if (o is IConfigurationSectionHandler)
513 return (IConfigurationSectionHandler) o;
515 o = CreateNewHandler (sectionName, (SectionData) o);
516 factories [sectionName] = o;
521 object CreateNewHandler (string sectionName, SectionData section)
523 Type t = Type.GetType (section.TypeName);
525 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
527 Type iconfig = typeof (IConfigurationSectionHandler);
528 if (!iconfig.IsAssignableFrom (t))
529 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
531 object o = Activator.CreateInstance (t, true);
533 throw new ConfigurationException ("Cannot get instance for " + t);
538 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
540 if (++i >= sectionPath.Length)
543 if (doc.DocumentElement == null)
546 XmlNode node = doc.DocumentElement.FirstChild;
547 while (node != null) {
548 if (node.Name == sectionPath [i]) {
549 ConfigXmlDocument result = new ConfigXmlDocument ();
550 result.Load (new StringReader (node.OuterXml));
551 return GetInnerDoc (result, i, sectionPath);
553 node = node.NextSibling;
559 XmlDocument GetDocumentForSection (string sectionName)
561 ConfigXmlDocument doc = new ConfigXmlDocument ();
565 string [] sectionPath = sectionName.Split ('/');
566 string outerxml = pending [sectionPath [0]] as string;
567 if (outerxml == null)
570 StringReader reader = new StringReader (outerxml);
571 XmlTextReader rd = new XmlTextReader (reader);
573 doc.LoadSingleElement (fileName, rd);
575 return GetInnerDoc (doc, 0, sectionPath);
578 object GetConfigInternal (string sectionName, HttpContext context, bool useLoc)
580 object handler = GetHandler (sectionName);
581 IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
585 object parentConfig = null;
586 if (parent != null) {
588 parentConfig = parent.GetConfig (sectionName, context);
590 parentConfig = parent.GetConfigOptLocation (sectionName, context, false);
593 XmlDocument doc = GetDocumentForSection (sectionName);
594 if (doc == null || doc.DocumentElement == null)
597 return iconf.Create (parentConfig, fileName, doc.DocumentElement);
600 string MakeRelative (string fullUrl, string relativeTo)
602 if (fullUrl == relativeTo)
605 if (fullUrl.IndexOf (relativeTo) != 0)
608 string leftOver = fullUrl.Substring (relativeTo.Length);
609 if (leftOver.Length > 0 && leftOver [0] == '/')
610 leftOver = leftOver.Substring (1);
612 leftOver = UrlUtils.Canonic (leftOver);
613 if (leftOver.Length > 0 && leftOver [0] == '/')
614 leftOver = leftOver.Substring (1);
620 public object GetConfig (string sectionName, HttpContext context)
622 if (locations != null && dirname != null) {
623 string reduced = MakeRelative (context.Request.CurrentExecutionFilePath, dirname);
624 string [] parts = reduced.Split ('/');
625 Location location = null;
627 string target = null;
628 for (int i = 0; i < parts.Length; i++) {
632 target = target + "/" + parts [i];
634 if (locations.ContainsKey (target)) {
635 location = locations [target] as Location;
636 } else if (locations.ContainsKey (target + "/*")) {
637 location = locations [target + "/*"] as Location;
641 if (location == null) {
642 location = locations ["*"] as Location;
645 if (location != null && location.Config != null) {
646 object o = location.Config.GetConfigOptLocation (sectionName, context, false);
653 return GetConfigOptLocation (sectionName, context, true);
656 object GetConfigOptLocation (string sectionName, HttpContext context, bool useLoc)
658 object config = this.FileCache [sectionName];
659 if (config == emptyMark)
666 config = GetConfigInternal (sectionName, context, useLoc);
667 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
673 private object LookForFactory (string key)
675 object o = factories [key];
680 return parent.LookForFactory (key);
685 private void InitRead (XmlTextReader reader)
687 reader.MoveToContent ();
688 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
689 ThrowException ("Configuration file does not have a valid root element", reader);
691 if (reader.HasAttributes)
692 ThrowException ("Unrecognized attribute in root element", reader);
694 MoveToNextElement (reader);
697 internal void MoveToNextElement (XmlTextReader reader)
699 while (reader.Read ()) {
700 XmlNodeType ntype = reader.NodeType;
701 if (ntype == XmlNodeType.Element)
704 if (ntype != XmlNodeType.Whitespace &&
705 ntype != XmlNodeType.Comment &&
706 ntype != XmlNodeType.SignificantWhitespace &&
707 ntype != XmlNodeType.EndElement)
708 ThrowException ("Unrecognized element", reader);
712 private void ReadSection (XmlTextReader reader, string sectionName)
715 string nameValue = null;
716 string typeValue = null;
717 string allowLoc = null, allowDef = null;
718 bool allowLocation = true;
719 AllowDefinition allowDefinition = AllowDefinition.Everywhere;
721 while (reader.MoveToNextAttribute ()) {
722 attName = reader.Name;
726 if (attName == "allowLocation") {
727 if (allowLoc != null)
728 ThrowException ("Duplicated allowLocation attribute.", reader);
730 allowLoc = reader.Value;
731 allowLocation = (allowLoc == "true");
732 if (!allowLocation && allowLoc != "false")
733 ThrowException ("Invalid attribute value", reader);
738 if (attName == "allowDefinition") {
739 if (allowDef != null)
740 ThrowException ("Duplicated allowDefinition attribute.", reader);
742 allowDef = reader.Value;
744 allowDefinition = (AllowDefinition) Enum.Parse (
745 typeof (AllowDefinition), allowDef);
747 ThrowException ("Invalid attribute value", reader);
753 if (attName == "type") {
754 if (typeValue != null)
755 ThrowException ("Duplicated type attribute.", reader);
756 typeValue = reader.Value;
760 if (attName == "name") {
761 if (nameValue != null)
762 ThrowException ("Duplicated name attribute.", reader);
764 nameValue = reader.Value;
765 if (nameValue == "location")
766 ThrowException ("location is a reserved section name", reader);
770 ThrowException ("Unrecognized attribute.", reader);
773 if (nameValue == null || typeValue == null)
774 ThrowException ("Required attribute missing", reader);
776 if (sectionName != null)
777 nameValue = sectionName + '/' + nameValue;
779 reader.MoveToElement();
780 object o = LookForFactory (nameValue);
781 if (o != null && o != removedMark)
782 ThrowException ("Already have a factory for " + nameValue, reader);
784 SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
785 section.FileName = fileName;
786 factories [nameValue] = section;
787 MoveToNextElement (reader);
790 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
792 if (!reader.MoveToNextAttribute () || reader.Name != "name")
793 ThrowException ("Unrecognized attribute.", reader);
795 string removeValue = reader.Value;
796 if (removeValue == null || removeValue.Length == 0)
797 ThrowException ("Empty name to remove", reader);
799 reader.MoveToElement ();
801 if (sectionName != null)
802 removeValue = sectionName + '/' + removeValue;
804 object o = LookForFactory (removeValue);
805 if (o != null && o == removedMark)
806 ThrowException ("No factory for " + removeValue, reader);
808 factories [removeValue] = removedMark;
809 MoveToNextElement (reader);
812 private void ReadSectionGroup (XmlTextReader reader, string configSection)
814 if (!reader.MoveToNextAttribute ())
815 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
820 if (reader.Name == "name") {
822 ThrowException ("Duplicate 'name' attribute.", reader);
823 value = reader.Value;
825 else if (reader.Name != "type")
826 ThrowException ("Unrecognized attribute.", reader);
827 } while (reader.MoveToNextAttribute ());
829 if (reader.Name != "name")
830 ThrowException ("Unrecognized attribute.", reader);
832 if (reader.MoveToNextAttribute ())
833 ThrowException ("Unrecognized attribute.", reader);
835 value = reader.Value;
838 ThrowException ("No 'name' attribute.", reader);
840 if (value == "location")
841 ThrowException ("location is a reserved section name", reader);
843 if (configSection != null)
844 value = configSection + '/' + value;
846 object o = LookForFactory (value);
847 if (o != null && o != removedMark && o != groupMark)
848 ThrowException ("Already have a factory for " + value, reader);
850 factories [value] = groupMark;
851 MoveToNextElement (reader);
852 ReadSections (reader, value);
855 private void ReadSections (XmlTextReader reader, string configSection)
857 int depth = reader.Depth;
858 while (reader.Depth == depth) {
859 string name = reader.Name;
860 if (name == "section") {
861 ReadSection (reader, configSection);
865 if (name == "remove") {
866 ReadRemoveSection (reader, configSection);
870 if (name == "clear") {
871 if (reader.HasAttributes)
872 ThrowException ("Unrecognized attribute.", reader);
875 MoveToNextElement (reader);
879 if (name == "sectionGroup") {
880 ReadSectionGroup (reader, configSection);
884 ThrowException ("Unrecognized element: " + reader.Name, reader);
888 void StoreLocation (string name, XmlTextReader reader)
891 bool haveAllow = false;
892 bool allowOverride = true;
895 while (reader.MoveToNextAttribute ()) {
900 ThrowException ("Duplicate path attribute", reader);
903 if (path.StartsWith ("."))
904 ThrowException ("Path cannot begin with '.'", reader);
906 if (path.IndexOfAny (forbiddenPathChars) != -1)
907 ThrowException ("Path cannot contain " + forbiddenStr, reader);
912 if (att == "allowOverride") {
914 ThrowException ("Duplicate allowOverride attribute", reader);
917 allowOverride = (reader.Value == "true");
918 if (!allowOverride && reader.Value != "false")
919 ThrowException ("allowOverride must be either true or false", reader);
923 ThrowException ("Unrecognized attribute.", reader);
927 return; // empty location tag
929 Location loc = new Location (this, path, allowOverride);
930 if (locations == null)
931 locations = new Hashtable ();
932 else if (locations.ContainsKey (loc.Path))
933 ThrowException ("Duplicated location path: " + loc.Path, reader);
935 reader.MoveToElement ();
936 loc.LoadFromString (reader.ReadOuterXml ());
937 locations [loc.Path] = loc;
938 if (!loc.AllowOverride) {
939 XmlTextReader inner = loc.GetReader ();
941 MoveToNextElement (inner);
942 ReadConfig (loc.GetReader (), true);
949 void StorePending (string name, XmlTextReader reader)
952 pending = new Hashtable ();
954 if (pending.ContainsKey (name))
955 ThrowException ("Sections can only appear once: " + name, reader);
957 pending [name] = reader.ReadOuterXml ();
960 void ReadConfig (XmlTextReader reader, bool isLocation)
962 int depth = reader.Depth;
963 while (!reader.EOF && reader.Depth == depth) {
964 string name = reader.Name;
966 if (name == "configSections") {
968 ThrowException ("<configSections> inside <location>", reader);
970 if (reader.HasAttributes)
971 ThrowException ("Unrecognized attribute in <configSections>.", reader);
973 MoveToNextElement (reader);
974 if (reader.Depth > depth)
975 ReadSections (reader, null);
976 } else if (name == "location") {
978 ThrowException ("<location> inside <location>", reader);
980 StoreLocation (name, reader);
981 MoveToNextElement (reader);
982 } else if (name != null && name != ""){
983 StorePending (name, reader);
984 MoveToNextElement (reader);
986 MoveToNextElement (reader);
991 void ThrowException (string text, XmlTextReader reader)
993 throw new ConfigurationException (text, fileName, reader.LineNumber);
1001 ConfigurationData parent;
1002 ConfigurationData thisOne;
1005 public Location (ConfigurationData parent, string path, bool allowOverride)
1007 this.parent = parent;
1008 this.allowOverride = allowOverride;
1009 this.path = (path == null || path == "") ? "*" : path;
1012 public bool AllowOverride {
1013 get { return (path != "*" || allowOverride); }
1016 public string Path {
1017 get { return path; }
1020 public string XmlStr {
1021 set { xmlstr = value; }
1024 public void LoadFromString (string str)
1027 throw new ArgumentNullException ("str");
1029 if (thisOne != null)
1030 throw new InvalidOperationException ();
1032 this.xmlstr = str.Trim ();
1036 XmlTextReader reader = GetReader ();
1037 thisOne = new ConfigurationData (parent, parent.FileName);
1038 thisOne.LoadFromReader (reader, parent.FileName, true);
1041 public XmlTextReader GetReader ()
1046 XmlTextReader reader = new XmlTextReader (new StringReader (xmlstr));
1047 reader.ReadStartElement ("location");
1051 public ConfigurationData Config {
1052 get { return thisOne; }