Merge pull request #4542 from lateralusX/jlorenss/win-fix-unwind-tramp-reg-aot
[mono.git] / mcs / class / System.Configuration / System.Configuration / Configuration.cs
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         {
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                 
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                 
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                 
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                 
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                 
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
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                 
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                 
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                 
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                 
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                 
327                 internal void SetConfigurationSection (SectionInfo config, ConfigurationSection sec)
328                 {
329                         elementData [config] = sec;
330                 }
331                 
332                 internal void SetSectionXml (SectionInfo config, string data)
333                 {
334                         elementData [config] = data;
335                 }
336                 
337                 internal string GetSectionXml (SectionInfo config)
338                 {
339                         return elementData [config] as string;
340                 }
341                 
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                 
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                 
381                 internal void RemoveConfigInfo (ConfigInfo config)
382                 {
383                         elementData.Remove (config);
384                 }
385                 
386                 public void Save ()
387                 {
388                         Save (ConfigurationSaveMode.Modified, false);
389                 }
390                 
391                 public void Save (ConfigurationSaveMode saveMode)
392                 {
393                         Save (saveMode, false);
394                 }
395                 
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                 
426                 public void SaveAs (string filename)
427                 {
428                         SaveAs (filename, ConfigurationSaveMode.Modified, false);
429                 }
430                 
431                 public void SaveAs (string filename, ConfigurationSaveMode saveMode)
432                 {
433                         SaveAs (filename, saveMode, false);
434                 }
435
436                 [MonoInternalNote ("Detect if file has changed")]
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
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                 
489                 void SaveData (XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)
490                 {
491                         rootGroup.WriteRootData (tw, this, mode);
492                 }
493
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
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                 
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
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
575                 internal void ReadData (XmlReader reader, bool allowOverride)
576                 {
577                         rootGroup.ReadData (this, reader, allowOverride);
578                 }
579                 
580
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