1 //
2 // System.Configuration.Configuration.cs
3 //
4 // Authors:
5 //	Duncan Mak (duncan@ximian.com)
6 // 	Lluis Sanchez Gual (lluis@novell.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining
9 // a copy of this software and associated documentation files (the
10 // "Software"), to deal in the Software without restriction, including
11 // without limitation the rights to use, copy, modify, merge, publish,
12 // distribute, sublicense, and/or sell copies of the Software, and to
13 // permit persons to whom the Software is furnished to do so, subject to
14 // the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be
17 // included in all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 //
27 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
28 //
29 
30 using System;
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.Configuration.Internal;
34 using System.ComponentModel;
35 using System.Reflection;
36 using System.Xml;
37 using System.IO;
38 
39 namespace System.Configuration {
40 
41 	// For configuration document, use this XmlDocument instead of the standard one. This ignores xmlns attribute for MS.
42 	internal class ConfigurationXmlDocument : XmlDocument
43 	{
CreateElement(string prefix, string localName, string namespaceURI)44 		public override XmlElement CreateElement (string prefix, string localName, string namespaceURI)
45 		{
46 			if (namespaceURI == "http://schemas.microsoft.com/.NetConfiguration/v2.0")
47 				return base.CreateElement (String.Empty, localName, String.Empty);
48 			else
49 				return base.CreateElement (prefix, localName, namespaceURI);
50 		}
51 	}
52 
53 	public sealed class Configuration
54 	{
55 		Configuration parent;
56 		Hashtable elementData = new Hashtable ();
57 		string streamName;
58 		ConfigurationSectionGroup rootSectionGroup;
59 		ConfigurationLocationCollection locations;
60 		SectionGroupInfo rootGroup;
61 		IConfigSystem system;
62 		bool hasFile;
63 		string rootNamespace;
64 
65 		string configPath;
66 		string locationConfigPath;
67 		string locationSubPath;
68 
69 		internal static event ConfigurationSaveEventHandler SaveStart;
70 		internal static event ConfigurationSaveEventHandler SaveEnd;
71 
Configuration(Configuration parent, string locationSubPath)72 		internal Configuration (Configuration parent, string locationSubPath)
73 		{
74 			this.parent = parent;
75 			this.system = parent.system;
76 			this.rootGroup = parent.rootGroup;
77 			this.locationSubPath = locationSubPath;
78 			this.configPath = parent.ConfigPath;
79 		}
80 
Configuration(InternalConfigurationSystem system, string locationSubPath)81 		internal Configuration (InternalConfigurationSystem system, string locationSubPath)
82 		{
83 			hasFile = true;
84 			this.system = system;
85 
86 			system.InitForConfiguration (ref locationSubPath, out configPath, out locationConfigPath);
87 
88 			Configuration parent = null;
89 
90 			if (locationSubPath != null) {
91 				parent = new Configuration (system, locationSubPath);
92 				if (locationConfigPath != null)
93 					parent = parent.FindLocationConfiguration (locationConfigPath, parent);
94 			}
95 
96 			Init (system, configPath, parent);
97 		}
98 
FindLocationConfiguration(string relativePath, Configuration defaultConfiguration)99 		internal Configuration FindLocationConfiguration (string relativePath, Configuration defaultConfiguration)
100 		{
101 			Configuration parentConfig = defaultConfiguration;
102 
103 			if (!String.IsNullOrEmpty (LocationConfigPath)) {
104 				Configuration parentFile = GetParentWithFile ();
105 				if (parentFile != null) {
106 					string parentRelativePath = system.Host.GetConfigPathFromLocationSubPath (configPath, relativePath);
107 					parentConfig = parentFile.FindLocationConfiguration (parentRelativePath, defaultConfiguration);
108 				}
109 			}
110 
111 			string relConfigPath = configPath.Substring (1) + "/";
112 			if (relativePath.StartsWith (relConfigPath, StringComparison.Ordinal))
113 				relativePath = relativePath.Substring (relConfigPath.Length);
114 
115 			ConfigurationLocation loc = Locations.FindBest (relativePath);
116 			if (loc == null)
117 				return parentConfig;
118 
119 			loc.SetParentConfiguration (parentConfig);
120 			return loc.OpenConfiguration ();
121 		}
122 
Init(IConfigSystem system, string configPath, Configuration parent)123 		internal void Init (IConfigSystem system, string configPath, Configuration parent)
124 		{
125 			this.system = system;
126 			this.configPath = configPath;
127 			streamName = system.Host.GetStreamName (configPath);
128 			this.parent = parent;
129 			if (parent != null)
130 				rootGroup = parent.rootGroup;
131 			else {
132 				rootGroup = new SectionGroupInfo ();
133 				rootGroup.StreamName = streamName;
134 			}
135 
136 			try {
137 				if (streamName != null)
138 					Load ();
139 			} catch (XmlException ex) {
140 				throw new ConfigurationErrorsException (ex.Message, ex, streamName, 0);
141 			}
142 		}
143 
144 		internal Configuration Parent {
145 			get { return parent; }
146 			set { parent = value; }
147 		}
148 
GetParentWithFile()149 		internal Configuration GetParentWithFile ()
150 		{
151 			Configuration parentFile = Parent;
152 			while (parentFile != null && !parentFile.HasFile)
153 				parentFile = parentFile.Parent;
154 			return parentFile;
155 		}
156 
157 		internal string FileName {
158 			get { return streamName; }
159 		}
160 
161 		internal IInternalConfigHost ConfigHost {
162 			get { return system.Host; }
163 		}
164 
165 		internal string LocationConfigPath {
166 			get { return locationConfigPath; }
167 		}
168 
GetLocationSubPath()169 		internal string GetLocationSubPath ()
170 		{
171 			Configuration confg = parent;
172 			string path = null;
173 			while (confg != null) {
174 				path = confg.locationSubPath;
175 				if (!String.IsNullOrEmpty (path))
176 					return path;
177 				confg = confg.parent;
178 			}
179 			return path;
180 		}
181 
182 		internal string ConfigPath {
183 			get { return configPath; }
184 		}
185 
186 		public AppSettingsSection AppSettings {
187 			get { return (AppSettingsSection) GetSection ("appSettings"); }
188 		}
189 
190 		public ConnectionStringsSection ConnectionStrings {
191 			get { return (ConnectionStringsSection) GetSection ("connectionStrings"); }
192 		}
193 
194 		// MSDN: If the value for this FilePath property represents a merged view and
195 		// no actual file exists for the application, the path to the parent configuration
196 		// file is returned.
197 		public string FilePath {
198 			get {
199 				if (streamName == null && parent != null)
200 					return parent.FilePath;
201 				return streamName;
202 			}
203 		}
204 
205 		public bool HasFile {
206 			get {
207 				return hasFile;
208 			}
209 		}
210 
211 		ContextInformation evaluationContext;
212 		public ContextInformation EvaluationContext {
213 			get {
214 				if (evaluationContext == null) {
215 					object ctx = system.Host.CreateConfigurationContext (configPath, GetLocationSubPath() );
216 					evaluationContext = new ContextInformation (this, ctx);
217 				}
218 
219 
220 				return evaluationContext;
221 			}
222 		}
223 
224 		public ConfigurationLocationCollection Locations {
225 			get {
226 				if (locations == null) locations = new ConfigurationLocationCollection ();
227 				return locations;
228 			}
229 		}
230 
231 		public bool NamespaceDeclared {
232 			get { return rootNamespace != null; }
233 			set { rootNamespace = value ? "http://schemas.microsoft.com/.NetConfiguration/v2.0" : null; }
234 		}
235 
236 		public ConfigurationSectionGroup RootSectionGroup {
237 			get {
238 				if (rootSectionGroup == null) {
239 					rootSectionGroup = new ConfigurationSectionGroup ();
240 					rootSectionGroup.Initialize (this, rootGroup);
241 				}
242 				return rootSectionGroup;
243 			}
244 		}
245 
246 		public ConfigurationSectionGroupCollection SectionGroups {
247 			get { return RootSectionGroup.SectionGroups; }
248 		}
249 
250 		public ConfigurationSectionCollection Sections {
251 			get { return RootSectionGroup.Sections; }
252 		}
253 
GetSection(string sectionName)254 		public ConfigurationSection GetSection (string sectionName)
255 		{
256 			string[] parts = sectionName.Split ('/');
257 			if (parts.Length == 1)
258 				return Sections [parts[0]];
259 
260 			ConfigurationSectionGroup group = SectionGroups [parts[0]];
261 			for (int n=1; group != null && n<parts.Length-1; n++)
262 				group = group.SectionGroups [parts [n]];
263 
264 			if (group != null)
265 				return group.Sections [parts [parts.Length - 1]];
266 			else
267 				return null;
268 		}
269 
GetSectionGroup(string sectionGroupName)270 		public ConfigurationSectionGroup GetSectionGroup (string sectionGroupName)
271 		{
272 			string[] parts = sectionGroupName.Split ('/');
273 			ConfigurationSectionGroup group = SectionGroups [parts[0]];
274 			for (int n=1; group != null && n<parts.Length; n++)
275 				group = group.SectionGroups [parts [n]];
276 			return group;
277 		}
278 
GetSectionInstance(SectionInfo config, bool createDefaultInstance)279 		internal ConfigurationSection GetSectionInstance (SectionInfo config, bool createDefaultInstance)
280 		{
281 			object data = elementData [config];
282 			ConfigurationSection sec = data as ConfigurationSection;
283 			if (sec != null || !createDefaultInstance) return sec;
284 
285 			object secObj = config.CreateInstance ();
286 			sec = secObj as ConfigurationSection;
287 			if (sec == null) {
288 				DefaultSection ds = new DefaultSection ();
289 				ds.SectionHandler = secObj as IConfigurationSectionHandler;
290 				sec = ds;
291 			}
292 			sec.Configuration = this;
293 
294 			ConfigurationSection parentSection = null;
295 			if (parent != null) {
296 				parentSection = parent.GetSectionInstance (config, true);
297 				sec.SectionInformation.SetParentSection (parentSection);
298 			}
299 			sec.SectionInformation.ConfigFilePath = FilePath;
300 
301 			sec.ConfigContext = system.Host.CreateDeprecatedConfigContext(configPath);
302 
303 			string xml = data as string;
304 			sec.RawXml = xml;
305 			sec.Reset (parentSection);
306 
307 			if (xml != null) {
308 				XmlTextReader r = new ConfigXmlTextReader (new StringReader (xml), FilePath);
309 				sec.DeserializeSection (r);
310 				r.Close ();
311 
312 				if (!String.IsNullOrEmpty (sec.SectionInformation.ConfigSource) && !String.IsNullOrEmpty (FilePath))
313 					sec.DeserializeConfigSource (Path.GetDirectoryName (FilePath));
314 			}
315 
316 			elementData [config] = sec;
317 			return sec;
318 		}
319 
GetSectionGroupInstance(SectionGroupInfo group)320 		internal ConfigurationSectionGroup GetSectionGroupInstance (SectionGroupInfo group)
321 		{
322 			ConfigurationSectionGroup gr = group.CreateInstance () as ConfigurationSectionGroup;
323 			if (gr != null) gr.Initialize (this, group);
324 			return gr;
325 		}
326 
SetConfigurationSection(SectionInfo config, ConfigurationSection sec)327 		internal void SetConfigurationSection (SectionInfo config, ConfigurationSection sec)
328 		{
329 			elementData [config] = sec;
330 		}
331 
SetSectionXml(SectionInfo config, string data)332 		internal void SetSectionXml (SectionInfo config, string data)
333 		{
334 			elementData [config] = data;
335 		}
336 
GetSectionXml(SectionInfo config)337 		internal string GetSectionXml (SectionInfo config)
338 		{
339 			return elementData [config] as string;
340 		}
341 
CreateSection(SectionGroupInfo group, string name, ConfigurationSection sec)342 		internal void CreateSection (SectionGroupInfo group, string name, ConfigurationSection sec)
343 		{
344 			if (group.HasChild (name))
345 				throw new ConfigurationErrorsException ("Cannot add a ConfigurationSection. A section or section group already exists with the name '" + name + "'");
346 
347 			if (!HasFile && !sec.SectionInformation.AllowLocation)
348 				throw new ConfigurationErrorsException ("The configuration section <" + name + "> cannot be defined inside a <location> element.");
349 
350 			if (!system.Host.IsDefinitionAllowed (configPath, sec.SectionInformation.AllowDefinition, sec.SectionInformation.AllowExeDefinition)) {
351 				object ctx = sec.SectionInformation.AllowExeDefinition != ConfigurationAllowExeDefinition.MachineToApplication ? (object) sec.SectionInformation.AllowExeDefinition : (object) sec.SectionInformation.AllowDefinition;
352 				throw new ConfigurationErrorsException ("The section <" + name + "> can't be defined in this configuration file (the allowed definition context is '" + ctx + "').");
353 			}
354 
355 			if (sec.SectionInformation.Type == null)
356 				sec.SectionInformation.Type = system.Host.GetConfigTypeName (sec.GetType ());
357 
358 			SectionInfo section = new SectionInfo (name, sec.SectionInformation);
359 			section.StreamName = streamName;
360 			section.ConfigHost = system.Host;
361 			group.AddChild (section);
362 			elementData [section] = sec;
363 			sec.Configuration = this;
364 		}
365 
CreateSectionGroup(SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec)366 		internal void CreateSectionGroup (SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec)
367 		{
368 			if (parentGroup.HasChild (name)) throw new ConfigurationErrorsException ("Cannot add a ConfigurationSectionGroup. A section or section group already exists with the name '" + name + "'");
369 			if (sec.Type == null) sec.Type = system.Host.GetConfigTypeName (sec.GetType ());
370 			sec.SetName (name);
371 
372 			SectionGroupInfo section = new SectionGroupInfo (name, sec.Type);
373 			section.StreamName = streamName;
374 			section.ConfigHost = system.Host;
375 			parentGroup.AddChild (section);
376 			elementData [section] = sec;
377 
378 			sec.Initialize (this, section);
379 		}
380 
RemoveConfigInfo(ConfigInfo config)381 		internal void RemoveConfigInfo (ConfigInfo config)
382 		{
383 			elementData.Remove (config);
384 		}
385 
Save()386 		public void Save ()
387 		{
388 			Save (ConfigurationSaveMode.Modified, false);
389 		}
390 
Save(ConfigurationSaveMode saveMode)391 		public void Save (ConfigurationSaveMode saveMode)
392 		{
393 			Save (saveMode, false);
394 		}
395 
Save(ConfigurationSaveMode saveMode, bool forceSaveAll)396 		public void Save (ConfigurationSaveMode saveMode, bool forceSaveAll)
397 		{
398 			if (!forceSaveAll && (saveMode != ConfigurationSaveMode.Full) && !HasValues (saveMode)) {
399 				ResetModified ();
400 				return;
401 			}
402 
403 			ConfigurationSaveEventHandler saveStart = SaveStart;
404 			ConfigurationSaveEventHandler saveEnd = SaveEnd;
405 
406 			object ctx = null;
407 			Exception saveEx = null;
408 			Stream stream = system.Host.OpenStreamForWrite (streamName, null, ref ctx);
409 			try {
410 				if (saveStart != null)
411 					saveStart (this, new ConfigurationSaveEventArgs (streamName, true, null, ctx));
412 
413 				Save (stream, saveMode, forceSaveAll);
414 				system.Host.WriteCompleted (streamName, true, ctx);
415 			} catch (Exception ex) {
416 				saveEx = ex;
417 				system.Host.WriteCompleted (streamName, false, ctx);
418 				throw;
419 			} finally {
420 				stream.Close ();
421 				if (saveEnd != null)
422 					saveEnd (this, new ConfigurationSaveEventArgs (streamName, false, saveEx, ctx));
423 			}
424 		}
425 
SaveAs(string filename)426 		public void SaveAs (string filename)
427 		{
428 			SaveAs (filename, ConfigurationSaveMode.Modified, false);
429 		}
430 
SaveAs(string filename, ConfigurationSaveMode saveMode)431 		public void SaveAs (string filename, ConfigurationSaveMode saveMode)
432 		{
433 			SaveAs (filename, saveMode, false);
434 		}
435 
436 		[MonoInternalNote ("Detect if file has changed")]
SaveAs(string filename, ConfigurationSaveMode saveMode, bool forceSaveAll)437 		public void SaveAs (string filename, ConfigurationSaveMode saveMode, bool forceSaveAll)
438 		{
439 			if (!forceSaveAll && (saveMode != ConfigurationSaveMode.Full) && !HasValues (saveMode)) {
440 				ResetModified ();
441 				return;
442 			}
443 
444 			string dir = Path.GetDirectoryName (Path.GetFullPath (filename));
445 			if (!Directory.Exists (dir))
446 				Directory.CreateDirectory (dir);
447 			Save (new FileStream (filename, FileMode.OpenOrCreate, FileAccess.Write), saveMode, forceSaveAll);
448 		}
449 
Save(Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll)450 		void Save (Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll)
451 		{
452 			XmlTextWriter tw = new XmlTextWriter (new StreamWriter (stream));
453 			tw.Formatting = Formatting.Indented;
454 			try {
455 				tw.WriteStartDocument ();
456 				if (rootNamespace != null)
457 					tw.WriteStartElement ("configuration", rootNamespace);
458 				else
459 					tw.WriteStartElement ("configuration");
460 				if (rootGroup.HasConfigContent (this)) {
461 					rootGroup.WriteConfig (this, tw, mode);
462 				}
463 
464 				foreach (ConfigurationLocation loc in Locations) {
465 					if (loc.OpenedConfiguration == null) {
466 						tw.WriteRaw ("\n");
467 						tw.WriteRaw (loc.XmlContent);
468 					}
469 					else {
470 						tw.WriteStartElement ("location");
471 						tw.WriteAttributeString ("path", loc.Path);
472 						if (!loc.AllowOverride)
473 							tw.WriteAttributeString ("allowOverride", "false");
474 						loc.OpenedConfiguration.SaveData (tw, mode, forceUpdateAll);
475 						tw.WriteEndElement ();
476 					}
477 				}
478 
479 				SaveData (tw, mode, forceUpdateAll);
480 				tw.WriteEndElement ();
481 				ResetModified ();
482 			}
483 			finally {
484 				tw.Flush ();
485 				tw.Close ();
486 			}
487 		}
488 
SaveData(XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)489 		void SaveData (XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)
490 		{
491 			rootGroup.WriteRootData (tw, this, mode);
492 		}
493 
HasValues(ConfigurationSaveMode mode)494 		bool HasValues (ConfigurationSaveMode mode)
495 		{
496 			foreach (ConfigurationLocation loc in Locations) {
497 				if (loc.OpenedConfiguration == null)
498 					continue;
499 				if (loc.OpenedConfiguration.HasValues (mode))
500 					return true;
501 			}
502 
503 			return rootGroup.HasValues (this, mode);
504 		}
505 
ResetModified()506 		void ResetModified ()
507 		{
508 			foreach (ConfigurationLocation loc in Locations) {
509 				if (loc.OpenedConfiguration == null)
510 					continue;
511 				loc.OpenedConfiguration.ResetModified ();
512 			}
513 
514 			rootGroup.ResetModified (this);
515 		}
516 
Load()517 		bool Load ()
518 		{
519 			if (String.IsNullOrEmpty (streamName))
520 				return true;
521 
522 			Stream stream = null;
523 			try {
524 				stream = system.Host.OpenStreamForRead (streamName);
525 				if (stream == null)
526 					return false;
527 			} catch {
528 				return false;
529 			}
530 
531 			using (XmlTextReader reader = new ConfigXmlTextReader (stream, streamName)) {
532 				ReadConfigFile (reader, streamName);
533 			}
534 			ResetModified ();
535 			return true;
536 		}
537 
ReadConfigFile(XmlReader reader, string fileName)538 		void ReadConfigFile (XmlReader reader, string fileName)
539 		{
540 			reader.MoveToContent ();
541 
542 			if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
543 				ThrowException ("Configuration file does not have a valid root element", reader);
544 
545 			if (reader.HasAttributes) {
546 				while (reader.MoveToNextAttribute ()) {
547 					if (reader.LocalName == "xmlns") {
548 						rootNamespace = reader.Value;
549 						continue;
550 					}
551 					ThrowException (String.Format ("Unrecognized attribute '{0}' in root element", reader.LocalName), reader);
552 				}
553 			}
554 
555 			reader.MoveToElement ();
556 
557 			if (reader.IsEmptyElement) {
558 				reader.Skip ();
559 				return;
560 			}
561 
562 			reader.ReadStartElement ();
563 			reader.MoveToContent ();
564 
565 			if (reader.LocalName == "configSections") {
566 				if (reader.HasAttributes)
567 					ThrowException ("Unrecognized attribute in <configSections>.", reader);
568 
569 				rootGroup.ReadConfig (this, fileName, reader);
570 			}
571 
572 			rootGroup.ReadRootData (reader, this, true);
573 		}
574 
ReadData(XmlReader reader, bool allowOverride)575 		internal void ReadData (XmlReader reader, bool allowOverride)
576 		{
577 			rootGroup.ReadData (this, reader, allowOverride);
578 		}
579 
580 
ThrowException(string text, XmlReader reader)581 		private void ThrowException (string text, XmlReader reader)
582 		{
583 			IXmlLineInfo li = reader as IXmlLineInfo;
584 			throw new ConfigurationErrorsException (text, streamName, li != null ? li.LineNumber : 0);
585 		}
586 	}
587 }
588 
589