2 // System.Configuration.WebConfigurationSettings.cs
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (c) 2003 Novell, Inc. (http://www.novell.com)
11 using System.Configuration;
12 using System.Collections;
14 using System.Reflection;
15 using System.Web.Util;
18 namespace System.Web.Configuration
20 class WebConfigurationSettings
22 static IConfigurationSystem oldConfig;
23 static WebDefaultConfig config;
24 static string machineConfigPath;
25 const BindingFlags privStatic = BindingFlags.NonPublic | BindingFlags.Static;
27 private WebConfigurationSettings ()
31 public static void Init ()
33 lock (typeof (WebConfigurationSettings)) {
37 WebDefaultConfig settings = WebDefaultConfig.GetInstance ();
38 Type t = typeof (ConfigurationSettings);
39 MethodInfo changeConfig = t.GetMethod ("ChangeConfigurationSystem",
42 if (changeConfig == null)
43 throw new ConfigurationException ("Cannot find method CCS");
45 object [] args = new object [] {settings};
46 oldConfig = (IConfigurationSystem) changeConfig.Invoke (null, args);
51 public static void Init (HttpContext context)
54 config.Init (context);
57 public static object GetConfig (string sectionName)
59 return config.GetConfig (sectionName);
62 public static object GetConfig (string sectionName, HttpContext context)
64 return config.GetConfig (sectionName, context);
67 public static string MachineConfigPath {
69 lock (typeof (WebConfigurationSettings)) {
70 if (machineConfigPath != null)
71 return machineConfigPath;
73 Type t = oldConfig.GetType ();
74 MethodInfo getMC = t.GetMethod ("GetMachineConfigPath",
78 throw new ConfigurationException ("Cannot find method GMC");
80 machineConfigPath = (string) getMC.Invoke (null, null);
81 return machineConfigPath;
88 // class WebDefaultConfig: read configuration from machine.config file and application
89 // config file if available.
91 class WebDefaultConfig : IConfigurationSystem
93 static WebDefaultConfig instance;
94 Hashtable fileToConfig;
95 HttpContext firstContext;
98 static WebDefaultConfig ()
100 instance = new WebDefaultConfig ();
103 private WebDefaultConfig ()
105 fileToConfig = new Hashtable ();
108 public static WebDefaultConfig GetInstance ()
113 public object GetConfig (string sectionName)
115 HttpContext current = HttpContext.Current;
117 current = firstContext;
118 return GetConfig (sectionName, current);
121 public object GetConfig (string sectionName, HttpContext context)
126 ConfigurationData config = GetConfigFromFileName (context.Request.FilePath, context);
130 return config.GetConfig (sectionName, context);
133 ConfigurationData GetConfigFromFileName (string filepath, HttpContext context)
135 if (filepath == "") {
136 return (ConfigurationData) fileToConfig [WebConfigurationSettings.MachineConfigPath];
139 string dir = UrlUtils.GetDirectory (filepath);
140 if (fileToConfig.ContainsKey (dir)) {
141 ConfigurationData data = (ConfigurationData) fileToConfig [dir];
142 if (CheckFileCache (data))
146 string realpath = context.Request.MapPath (dir);
147 string lower = Path.Combine (realpath, "web.config");
148 string upper = Path.Combine (realpath, "Web.config");
149 bool isUpper = File.Exists (upper);
150 bool isLower = File.Exists (lower);
151 if (Path.DirectorySeparatorChar == '/' && isUpper && isLower)
152 throw new ConfigurationException ("Both web.config and Web.config exist for " + dir);
157 string wcfile = (isUpper) ? upper : (isLower) ? lower : null;
158 ConfigurationData parent = GetConfigFromFileName (dir, context);
162 ConfigurationData child = new ConfigurationData (parent);
164 child.LoadFromFile (wcfile);
165 fileToConfig [dir] = child;
169 bool CheckFileCache (ConfigurationData data)
174 if (data.FileCache [""] == FileWatcherCache.Changed)
177 return CheckFileCache (data.Parent);
182 // nothing. We need a context.
185 public void Init (HttpContext context)
194 firstContext = context;
195 ConfigurationData data = new ConfigurationData ();
196 if (!data.LoadFromFile (WebConfigurationSettings.MachineConfigPath))
197 throw new ConfigurationException ("Cannot find " + WebConfigurationSettings.MachineConfigPath);
199 fileToConfig [WebConfigurationSettings.MachineConfigPath] = data;
204 static string GetAppConfigPath ()
206 AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
208 string configFile = currentInfo.ConfigurationFile;
209 if (configFile == null || configFile.Length == 0)
218 // TODO: this should be changed to use the FileSystemWatcher
220 // -eric@5stops.com 9.20.2003
222 class FileWatcherCache
224 Hashtable cacheTable;
225 DateTime lastWriteTime;
227 public static readonly object Changed = new object ();
228 static TimeSpan seconds = new TimeSpan (0, 0, 2);
230 public FileWatcherCache (string filename)
232 cacheTable = Hashtable.Synchronized (new Hashtable ());
233 lastWriteTime = new FileInfo (filename).LastWriteTime;
234 this.filename = filename;
237 bool CheckFileChange ()
239 FileInfo info = new FileInfo (filename);
242 lastWriteTime = DateTime.MinValue;
247 DateTime writeTime = info.LastWriteTime;
248 TimeSpan ts = (info.LastWriteTime - lastWriteTime);
250 lastWriteTime = writeTime;
258 public object this [string key] {
260 if (!CheckFileChange ())
263 return cacheTable [key];
267 cacheTable [key] = value;
281 public readonly string SectionName;
282 public readonly string TypeName;
283 public readonly bool AllowLocation;
284 public readonly AllowDefinition AllowDefinition;
285 public string FileName;
287 public SectionData (string sectionName, string typeName,
288 bool allowLocation, AllowDefinition allowDefinition)
290 SectionName = sectionName;
292 AllowLocation = allowLocation;
293 AllowDefinition = allowDefinition;
297 class ConfigurationData
299 ConfigurationData parent;
305 static object removedMark = new object ();
306 static object groupMark = new object ();
307 static object emptyMark = new object ();
308 FileWatcherCache fileCache;
309 static char [] forbiddenPathChars = new char [] {
310 ';', '?', ':', '@', '&', '=', '+',
311 '$', ',','\\', '*', '\"', '<', '>'
314 static string forbiddenStr = "';', '?', ':', '@', '&', '=', '+', '$', ',', '\\', '*', '\"', '<', '>'";
316 internal FileWatcherCache FileCache {
319 if (fileCache != null)
322 fileCache = new FileWatcherCache (fileName);
329 internal string FileName {
330 get { return fileName; }
333 internal ConfigurationData Parent {
334 get { return parent; }
337 internal string DirName {
338 get { return dirname; }
339 set { dirname = value; }
343 public ConfigurationData () : this (null)
347 public ConfigurationData (ConfigurationData parent)
349 this.parent = (parent == this) ? null : parent;
350 factories = new Hashtable ();
353 public bool LoadFromFile (string fileName)
355 this.fileName = fileName;
356 if (fileName == null || !File.Exists (fileName))
359 XmlTextReader reader = null;
362 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
363 reader = new XmlTextReader (fs);
365 ReadConfig (reader, false);
366 } catch (ConfigurationException) {
368 } catch (Exception e) {
369 throw new ConfigurationException ("Error reading " + fileName, e);
378 public void LoadFromReader (XmlTextReader reader, string fakeFileName, bool isLocation)
380 fileName = fakeFileName;
381 MoveToNextElement (reader);
382 ReadConfig (reader, isLocation);
385 object GetHandler (string sectionName)
388 object o = factories [sectionName];
389 if (o == null || o == removedMark) {
391 return parent.GetHandler (sectionName);
396 if (o is IConfigurationSectionHandler)
397 return (IConfigurationSectionHandler) o;
399 o = CreateNewHandler (sectionName, (SectionData) o);
400 factories [sectionName] = o;
405 object CreateNewHandler (string sectionName, SectionData section)
407 Type t = Type.GetType (section.TypeName);
409 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
411 Type iconfig = typeof (IConfigurationSectionHandler);
412 if (!iconfig.IsAssignableFrom (t))
413 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
415 object o = Activator.CreateInstance (t, true);
417 throw new ConfigurationException ("Cannot get instance for " + t);
422 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
424 if (++i >= sectionPath.Length)
427 if (doc.DocumentElement == null)
430 XmlNode node = doc.DocumentElement.FirstChild;
431 while (node != null) {
432 if (node.Name == sectionPath [i]) {
433 ConfigXmlDocument result = new ConfigXmlDocument ();
434 result.Load (new StringReader (node.OuterXml));
435 return GetInnerDoc (result, i, sectionPath);
437 node = node.NextSibling;
443 XmlDocument GetDocumentForSection (string sectionName)
445 ConfigXmlDocument doc = new ConfigXmlDocument ();
449 string [] sectionPath = sectionName.Split ('/');
450 string outerxml = pending [sectionPath [0]] as string;
451 if (outerxml == null)
454 doc.Load (new StringReader (outerxml));
455 return GetInnerDoc (doc, 0, sectionPath);
458 object GetConfigInternal (string sectionName, HttpContext context, bool useLoc)
460 object handler = GetHandler (sectionName);
461 IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
465 object parentConfig = null;
466 if (parent != null) {
468 parentConfig = parent.GetConfig (sectionName, context);
470 parentConfig = parent.GetConfigOptLocation (sectionName, context, false);
473 XmlDocument doc = GetDocumentForSection (sectionName);
474 if (doc == null || doc.DocumentElement == null)
477 return iconf.Create (parentConfig, fileName, doc.DocumentElement);
480 public object GetConfig (string sectionName, HttpContext context)
482 if (locations != null && dirname != null) {
483 string reduced = UrlUtils.MakeRelative (context.Request.FilePath, dirname);
484 Location location = locations [reduced] as Location;
485 if (location == null) {
486 location = locations ["*"] as Location;
489 if (location != null && location.Config != null) {
490 object o = location.Config.GetConfigOptLocation (sectionName, context, false);
497 return GetConfigOptLocation (sectionName, context, true);
500 object GetConfigOptLocation (string sectionName, HttpContext context, bool useLoc)
502 object config = this.FileCache [sectionName];
503 if (config == emptyMark)
510 config = GetConfigInternal (sectionName, context, useLoc);
511 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
517 private object LookForFactory (string key)
519 object o = factories [key];
524 return parent.LookForFactory (key);
529 private void InitRead (XmlTextReader reader)
531 reader.MoveToContent ();
532 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
533 ThrowException ("Configuration file does not have a valid root element", reader);
535 if (reader.HasAttributes)
536 ThrowException ("Unrecognized attribute in root element", reader);
538 MoveToNextElement (reader);
541 internal void MoveToNextElement (XmlTextReader reader)
543 while (reader.Read ()) {
544 XmlNodeType ntype = reader.NodeType;
545 if (ntype == XmlNodeType.Element)
548 if (ntype != XmlNodeType.Whitespace &&
549 ntype != XmlNodeType.Comment &&
550 ntype != XmlNodeType.SignificantWhitespace &&
551 ntype != XmlNodeType.EndElement)
552 ThrowException ("Unrecognized element", reader);
556 private void ReadSection (XmlTextReader reader, string sectionName)
559 string nameValue = null;
560 string typeValue = null;
561 string allowLoc = null, allowDef = null;
562 bool allowLocation = true;
563 AllowDefinition allowDefinition = AllowDefinition.Everywhere;
565 while (reader.MoveToNextAttribute ()) {
566 attName = reader.Name;
570 if (attName == "allowLocation") {
571 if (allowLoc != null)
572 ThrowException ("Duplicated allowLocation attribute.", reader);
574 allowLoc = reader.Value;
575 allowLocation = (allowLoc == "true");
576 if (!allowLocation && allowLoc != "false")
577 ThrowException ("Invalid attribute value", reader);
582 if (attName == "allowDefinition") {
583 if (allowDef != null)
584 ThrowException ("Duplicated allowDefinition attribute.", reader);
586 allowDef = reader.Value;
588 allowDefinition = (AllowDefinition) Enum.Parse (
589 typeof (AllowDefinition), allowDef);
591 ThrowException ("Invalid attribute value", reader);
597 if (attName == "type") {
598 if (typeValue != null)
599 ThrowException ("Duplicated type attribute.", reader);
600 typeValue = reader.Value;
604 if (attName == "name") {
605 if (nameValue != null)
606 ThrowException ("Duplicated name attribute.", reader);
608 nameValue = reader.Value;
609 if (nameValue == "location")
610 ThrowException ("location is a reserved section name", reader);
614 ThrowException ("Unrecognized attribute.", reader);
617 if (nameValue == null || typeValue == null)
618 ThrowException ("Required attribute missing", reader);
620 if (sectionName != null)
621 nameValue = sectionName + '/' + nameValue;
623 reader.MoveToElement();
624 object o = LookForFactory (nameValue);
625 if (o != null && o != removedMark)
626 ThrowException ("Already have a factory for " + nameValue, reader);
628 SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
629 section.FileName = fileName;
630 factories [nameValue] = section;
631 MoveToNextElement (reader);
634 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
636 if (!reader.MoveToNextAttribute () || reader.Name != "name")
637 ThrowException ("Unrecognized attribute.", reader);
639 string removeValue = reader.Value;
640 if (removeValue == null || removeValue.Length == 0)
641 ThrowException ("Empty name to remove", reader);
643 reader.MoveToElement ();
645 if (sectionName != null)
646 removeValue = sectionName + '/' + removeValue;
648 object o = LookForFactory (removeValue);
649 if (o != null && o == removedMark)
650 ThrowException ("No factory for " + removeValue, reader);
652 factories [removeValue] = removedMark;
653 MoveToNextElement (reader);
656 private void ReadSectionGroup (XmlTextReader reader, string configSection)
658 if (!reader.MoveToNextAttribute ())
659 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
661 if (reader.Name != "name")
662 ThrowException ("Unrecognized attribute.", reader);
664 if (reader.MoveToNextAttribute ())
665 ThrowException ("Unrecognized attribute.", reader);
667 string value = reader.Value;
668 if (value == "location")
669 ThrowException ("location is a reserved section name", reader);
671 if (configSection != null)
672 value = configSection + '/' + value;
674 object o = LookForFactory (value);
675 if (o != null && o != removedMark && o != groupMark)
676 ThrowException ("Already have a factory for " + value, reader);
678 factories [value] = groupMark;
679 MoveToNextElement (reader);
680 ReadSections (reader, value);
683 private void ReadSections (XmlTextReader reader, string configSection)
685 int depth = reader.Depth;
686 while (reader.Depth == depth) {
687 string name = reader.Name;
688 if (name == "section") {
689 ReadSection (reader, configSection);
693 if (name == "remove") {
694 ReadRemoveSection (reader, configSection);
698 if (name == "clear") {
699 if (reader.HasAttributes)
700 ThrowException ("Unrecognized attribute.", reader);
703 MoveToNextElement (reader);
707 if (name == "sectionGroup") {
708 ReadSectionGroup (reader, configSection);
712 ThrowException ("Unrecognized element: " + reader.Name, reader);
716 void StoreLocation (string name, XmlTextReader reader)
718 if (locations == null) {
719 locations = new Hashtable ();
723 bool haveAllow = false;
724 bool allowOverride = true;
726 while (reader.MoveToNextAttribute ()) {
727 string att = reader.Name;
731 ThrowException ("Duplicate path attribute", reader);
734 if (path.StartsWith ("."))
735 ThrowException ("Path cannot begin with '.'", reader);
737 if (path.IndexOfAny (forbiddenPathChars) != -1)
738 ThrowException ("Path cannot contain " + forbiddenStr, reader);
743 if (att == "allowOverride") {
745 ThrowException ("Duplicate allowOverride attribute", reader);
748 allowOverride = (reader.Value == "true");
749 if (!allowOverride && reader.Value != "false")
750 ThrowException ("allowOverride must be either true or false", reader);
754 ThrowException ("Unrecognized attribute.", reader);
757 Location loc = new Location (this, path, allowOverride);
758 if (locations.ContainsKey (loc.Path))
759 ThrowException ("Duplicated location path: " + loc.Path, reader);
761 reader.MoveToElement ();
762 loc.LoadFromString (reader.ReadInnerXml ());
763 locations [loc.Path] = loc;
764 if (!loc.AllowOverride) {
765 XmlTextReader inner = loc.GetReader ();
767 MoveToNextElement (inner);
768 ReadConfig (loc.GetReader (), true);
775 void StorePending (string name, XmlTextReader reader)
778 pending = new Hashtable ();
780 if (pending.ContainsKey (name))
781 ThrowException ("Sections can only appear once: " + name, reader);
783 pending [name] = reader.ReadOuterXml ();
786 void ReadConfig (XmlTextReader reader, bool isLocation)
788 int depth = reader.Depth;
789 while (!reader.EOF && reader.Depth == depth) {
790 string name = reader.Name;
792 if (name == "configSections") {
794 ThrowException ("<configSections> inside <location>", reader);
796 if (reader.HasAttributes)
797 ThrowException ("Unrecognized attribute in <configSections>.", reader);
799 MoveToNextElement (reader);
800 ReadSections (reader, null);
801 } else if (name == "location") {
803 ThrowException ("<location> inside <location>", reader);
805 StoreLocation (name, reader);
806 MoveToNextElement (reader);
807 } else if (name != null && name != ""){
808 StorePending (name, reader);
809 MoveToNextElement (reader);
811 MoveToNextElement (reader);
816 void ThrowException (string text, XmlTextReader reader)
818 throw new ConfigurationException (text, fileName, reader.LineNumber);
826 ConfigurationData parent;
827 ConfigurationData thisOne;
830 public Location (ConfigurationData parent, string path, bool allowOverride)
832 this.parent = parent;
833 this.allowOverride = allowOverride;
834 this.path = (path == null || path == "") ? "*" : path;
837 public bool AllowOverride {
838 get { return (path != "*" || allowOverride); }
845 public string XmlStr {
846 set { xmlstr = value; }
849 public void LoadFromString (string str)
852 throw new ArgumentNullException ("str");
855 throw new InvalidOperationException ();
857 this.xmlstr = str.Trim ();
861 XmlTextReader reader = new XmlTextReader (new StringReader (str));
862 thisOne = new ConfigurationData (parent);
863 thisOne.LoadFromReader (reader, parent.FileName, true);
866 public XmlTextReader GetReader ()
871 XmlTextReader reader = new XmlTextReader (new StringReader (xmlstr));
875 public ConfigurationData Config {
876 get { return thisOne; }