Merge pull request #2548 from esdrubal/discovery
[mono.git] / mcs / class / System / System.Configuration / CustomizableFileSettingsProvider.cs
1 //
2 // CustomizableFileSettingsProvider.cs
3 //
4 // Authors:
5 //      Noriaki Okimoto  <seara@ojk.sppd.ne.jp>
6 //      Atsushi Enomoto  <atsushi@ximian.com>
7 //
8 // (C)2007 Noriaki Okimoto
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 #if CONFIGURATION_DEP
31
32 extern alias PrebuiltSystem;
33
34 using System;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Configuration;
38 using System.Globalization;
39 using System.IO;
40 using System.Reflection;
41 using System.Security.Cryptography;
42 using System.Text;
43 using System.Xml;
44
45 using NameValueCollection = PrebuiltSystem.System.Collections.Specialized.NameValueCollection;
46
47 namespace System.Configuration
48 {
49         // location to store user configuration settings.
50         internal enum UserConfigLocationOption : uint
51         {
52                 Product = 0x20,
53                 Product_VersionMajor = 0x21,
54                 Product_VersionMinor = 0x22,
55                 Product_VersionBuild = 0x24,
56                 Product_VersionRevision = 0x28,
57                 Company_Product = 0x30,
58                 Company_Product_VersionMajor = 0x31,
59                 Company_Product_VersionMinor = 0x32,
60                 Company_Product_VersionBuild = 0x34,
61                 Company_Product_VersionRevision = 0x38,
62                 Evidence = 0x40,
63                 Other = 0x8000
64         }
65         
66         internal class CustomizableFileSettingsProvider : SettingsProvider, IApplicationSettingsProvider
67         {
68                 // KLUDGE WARNING.
69                 //
70                 // This is used from within System.Web to allow mapping of the ExeConfigFilename to
71                 // the correct Web.config for the current request. Otherwise web applications will
72                 // not be able to access settings from Web.config. The type assigned to this
73                 // variable must descend from the ConfigurationFileMap class and its
74                 // MachineConfigFilename will be used to set the ExeConfigFilename.
75                 //
76                 // This is necessary to fix bug #491531
77 #pragma warning disable 649
78                 private static Type webConfigurationFileMapType;
79 #pragma warning restore 649
80                 
81                 private static string userRoamingPath = "";
82                 private static string userLocalPath = "";
83                 private static string userRoamingPathPrevVersion = "";
84                 private static string userLocalPathPrevVersion = "";
85                 private static string userRoamingName = "user.config";
86                 private static string userLocalName = "user.config";
87                 private static string userRoamingBasePath = "";
88                 private static string userLocalBasePath = "";
89                 private static string CompanyName = "";
90                 private static string ProductName = "";
91                 private static string ForceVersion = "";
92                 private static string[] ProductVersion;
93
94                 // whether to include parts in the folder name or not:
95                 private static bool isVersionMajor = false;     // 0x0001       major version
96                 private static bool isVersionMinor = false;     // 0x0002       minor version
97                 private static bool isVersionBuild = false;     // 0x0004       build version
98                 private static bool isVersionRevision = false;  // 0x0008       revision
99                 private static bool isCompany = true;           // 0x0010       corporate name
100                 private static bool isProduct = true;           // 0x0020       product name
101                 private static bool isEvidence = false;         // 0x0040       evidence hash
102
103                 private static bool userDefine = false;         // 0x8000       ignore all above and use user definition
104
105                 private static UserConfigLocationOption userConfig = UserConfigLocationOption.Company_Product;
106
107                 public override void Initialize (string name, NameValueCollection config)
108                 {
109                         base.Initialize (name, config);
110                 }
111
112                 // full path to roaming user.config
113                 internal static string UserRoamingFullPath {
114                         get { return Path.Combine (userRoamingPath, userRoamingName); }
115                 }
116
117                 // full path to local user.config
118                 internal static string UserLocalFullPath {
119                         get { return Path.Combine (userLocalPath, userLocalName); }
120                 }
121
122                 // previous full path to roaming user.config
123                 public static string PrevUserRoamingFullPath {
124                         get { return Path.Combine (userRoamingPathPrevVersion, userRoamingName); }
125                 }
126
127                 // previous full path to local user.config
128                 public static string PrevUserLocalFullPath {
129                         get { return Path.Combine (userLocalPathPrevVersion, userLocalName); }
130                 }
131
132                 // path to roaming user.config
133                 public static string UserRoamingPath {
134                         get { return userRoamingPath; }
135                 }
136
137                 // path to local user.config
138                 public static string UserLocalPath {
139                         get { return userLocalPath; }
140                 }
141
142                 // file name which is equivalent to user.config, for roaming user
143                 public static string UserRoamingName {
144                         get { return userRoamingName; }
145                 }
146
147                 // file name which is equivalent to user.config, for local user
148                 public static string UserLocalName {
149                         get { return userLocalName; }
150                 }
151
152                 public static UserConfigLocationOption UserConfigSelector
153                 {
154                         get { return userConfig; }
155                         set {
156                                 userConfig = value;
157
158                                 if (((uint) userConfig & 0x8000) != 0) {
159                                         isVersionMajor = false;
160                                         isVersionMinor = false;
161                                         isVersionBuild = false;
162                                         isVersionRevision = false;
163                                         isCompany = false;
164                                         return;
165                                 }
166
167                                 isVersionRevision = ((uint) userConfig & 0x0008) != 0;
168                                 isVersionBuild = isVersionRevision | ((uint)userConfig & 0x0004) != 0;
169                                 isVersionMinor = isVersionBuild | ((uint)userConfig & 0x0002) != 0;
170                                 isVersionMajor = IsVersionMinor | ((uint)userConfig & 0x0001) != 0;
171
172                                 isCompany = ((uint) userConfig & 0x0010) != 0;
173                                 isProduct = ((uint) userConfig & 0x0020) != 0;
174                         }
175                 }
176
177                 // whether the path to include the major version.
178                 public static bool IsVersionMajor
179                 {
180                         get { return isVersionMajor; }
181                         set
182                         {
183                                 isVersionMajor = value;
184                                 isVersionMinor = false;
185                                 isVersionBuild = false;
186                                 isVersionRevision = false;
187                         }
188                 }
189
190                 // whether the path to include minor version.
191                 public static bool IsVersionMinor
192                 {
193                         get { return isVersionMinor; }
194                         set
195                         {
196                                 isVersionMinor = value;
197                                 if (isVersionMinor)
198                                         isVersionMajor = true;
199                                 isVersionBuild = false;
200                                 isVersionRevision = false;
201                         }
202                 }
203
204                 // whether the path to include build version.
205                 public static bool IsVersionBuild
206                 {
207                         get { return isVersionBuild; }
208                         set
209                         {
210                                 isVersionBuild = value;
211                                 if (isVersionBuild) {
212                                         isVersionMajor = true;
213                                         isVersionMinor = true;
214                                 }
215                                 isVersionRevision = false;
216                         }
217                 }
218
219                 // whether the path to include revision.
220                 public static bool IsVersionRevision
221                 {
222                         get { return isVersionRevision; }
223                         set
224                         {
225                                 isVersionRevision = value;
226                                 if (isVersionRevision) {
227                                         isVersionMajor = true;
228                                         isVersionMinor = true;
229                                         isVersionBuild = true;
230                                 }
231                         }
232                 }
233
234                 // whether the path to include company name.
235                 public static bool IsCompany
236                 {
237                         get { return isCompany; }
238                         set { isCompany = value; }
239                 }
240
241                 // whether the path to include evidence hash.
242                 public static bool IsEvidence
243                 {
244                         get { return isEvidence; }
245                         set { isEvidence = value; }
246                 }
247
248                 // AssemblyCompanyAttribute->Namespace->"Program"
249                 private static string GetCompanyName ()
250                 {
251                         Assembly assembly = Assembly.GetEntryAssembly ();
252                         if (assembly == null)
253                                 assembly = Assembly.GetCallingAssembly ();
254
255                         AssemblyCompanyAttribute [] attrs = (AssemblyCompanyAttribute []) assembly.GetCustomAttributes (typeof (AssemblyCompanyAttribute), true);
256                 
257                         if ((attrs != null) && attrs.Length > 0) {
258                                 return attrs [0].Company;
259                         }
260
261                         MethodInfo entryPoint = assembly.EntryPoint;
262                         Type entryType = entryPoint != null ? entryPoint.DeclaringType : null;
263                         if (entryType != null && !String.IsNullOrEmpty (entryType.Namespace)) {
264                                 int end = entryType.Namespace.IndexOf ('.');
265                                 return end < 0 ? entryType.Namespace : entryType.Namespace.Substring (0, end);
266                         }
267                         return "Program";
268                 }
269
270                 private static string GetProductName ()
271                 {
272                         Assembly assembly = Assembly.GetEntryAssembly ();
273                         if (assembly == null)
274                                 assembly = Assembly.GetCallingAssembly ();
275
276                         byte [] pkt = assembly.GetName ().GetPublicKeyToken ();
277                         return String.Format ("{0}_{1}_{2}",
278                                 AppDomain.CurrentDomain.FriendlyName,
279                                 pkt != null && pkt.Length > 0 ? "StrongName" : "Url",
280                                 GetEvidenceHash());
281                 }
282
283                 // Note: Changed from base64() to hex output to avoid unexpected chars like '\' or '/' with filesystem meaning.
284                 //       Otherwise eventually filenames, which are invalid on linux or windows, might be created.
285                 // Signed-off-by:  Carsten Schlote <schlote@vahanus.net>
286                 // TODO: Compare with .NET. It might be also, that their way isn't suitable for Unix OS derivates (slahes in output)
287                 private static string GetEvidenceHash ()
288                 {
289                         Assembly assembly = Assembly.GetEntryAssembly ();
290                         if (assembly == null)
291                                 assembly = Assembly.GetCallingAssembly ();
292
293                         byte [] pkt = assembly.GetName ().GetPublicKeyToken ();
294                         byte [] hash = SHA1.Create ().ComputeHash (pkt != null && pkt.Length >0 ? pkt : Encoding.UTF8.GetBytes (assembly.EscapedCodeBase));
295                         System.Text.StringBuilder evidence_string = new System.Text.StringBuilder();
296                         foreach (byte b in hash)
297                                 evidence_string.AppendFormat("{0:x2}",b);
298                         return evidence_string.ToString ();
299                 }
300
301                 private static string GetProductVersion ()
302                 {
303                         Assembly assembly = Assembly.GetEntryAssembly ();
304                         if (assembly == null)
305                                 assembly = Assembly.GetCallingAssembly ();
306                         if (assembly == null)
307                                 return string.Empty;
308
309                         return assembly.GetName ().Version.ToString ();
310                 }
311
312                 private static void CreateUserConfigPath ()
313                 {
314                         if (userDefine)
315                                 return;
316
317                         if (ProductName == "")
318                                 ProductName = GetProductName ();
319                         if (CompanyName == "")
320                                 CompanyName = GetCompanyName ();
321                         if (ForceVersion == "")
322                                 ProductVersion = GetProductVersion ().Split('.');
323
324                         // C:\Documents and Settings\(user)\Application Data
325                         if (userRoamingBasePath == "")
326                                 userRoamingPath = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
327                         else
328                                 userRoamingPath = userRoamingBasePath;
329
330                         // C:\Documents and Settings\(user)\Local Settings\Application Data (on Windows)
331                         if (userLocalBasePath == "")
332                                 userLocalPath = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData);
333                         else
334                                 userLocalPath = userLocalBasePath;
335
336                         if (isCompany) {
337                                 userRoamingPath = Path.Combine (userRoamingPath, CompanyName);
338                                 userLocalPath = Path.Combine (userLocalPath, CompanyName);
339                         }
340
341                         if (isProduct) {
342                                 if (isEvidence) {
343                                         Assembly assembly = Assembly.GetEntryAssembly ();
344                                         if (assembly == null)
345                                                 assembly = Assembly.GetCallingAssembly ();
346                                         byte [] pkt = assembly.GetName ().GetPublicKeyToken ();
347                                         ProductName = String.Format ("{0}_{1}_{2}",
348                                                 ProductName,
349                                                 pkt != null ? "StrongName" : "Url",
350                                                 GetEvidenceHash());
351                                 }
352                                 userRoamingPath = Path.Combine (userRoamingPath, ProductName);
353                                 userLocalPath = Path.Combine (userLocalPath, ProductName);
354                                 
355                         }
356
357                         string versionName;
358
359                         if (ForceVersion == "") {
360                                 if (isVersionRevision)
361                                         versionName = String.Format ("{0}.{1}.{2}.{3}", ProductVersion [0], ProductVersion [1], ProductVersion [2], ProductVersion [3]);
362                                 else if (isVersionBuild)
363                                         versionName = String.Format ("{0}.{1}.{2}", ProductVersion [0], ProductVersion [1], ProductVersion [2]);
364                                 else if (isVersionMinor)
365                                         versionName = String.Format ("{0}.{1}", ProductVersion [0], ProductVersion [1]);
366                                 else if (isVersionMajor)
367                                         versionName = ProductVersion [0];
368                                 else
369                                         versionName = "";
370                         }
371                         else
372                                 versionName = ForceVersion;
373
374                         string prevVersionRoaming = PrevVersionPath (userRoamingPath, versionName);
375                         string prevVersionLocal = PrevVersionPath (userLocalPath, versionName);
376                         
377                         userRoamingPath = Path.Combine (userRoamingPath, versionName);
378                         userLocalPath = Path.Combine (userLocalPath, versionName);
379                         if (prevVersionRoaming != "")
380                                 userRoamingPathPrevVersion = Path.Combine(userRoamingPath, prevVersionRoaming);
381                         if (prevVersionLocal != "")
382                                 userLocalPathPrevVersion = Path.Combine(userLocalPath, prevVersionLocal);
383                 }
384
385                 // string for the previous version. It ignores newer ones.
386                 private static string PrevVersionPath (string dirName, string currentVersion)
387                 {
388                         string prevVersionString = "";
389
390                         if (!Directory.Exists(dirName))
391                                 return prevVersionString;
392                         DirectoryInfo currentDir = new DirectoryInfo (dirName);
393                         foreach (DirectoryInfo dirInfo in currentDir.GetDirectories ())
394                                 if (String.Compare (currentVersion, dirInfo.Name, StringComparison.Ordinal) > 0)
395                                         if (String.Compare (prevVersionString, dirInfo.Name, StringComparison.Ordinal) < 0)
396                                                 prevVersionString = dirInfo.Name;
397
398                         return prevVersionString;
399                 }
400
401                 // sets the explicit path to store roaming user.config or equivalent.
402                 // (returns the path validity.)
403                 public static bool SetUserRoamingPath (string configPath)
404                 {
405                         if (CheckPath (configPath))
406                         {
407                                 userRoamingBasePath = configPath;
408                                 return true;
409                         }
410                         else
411                                 return false;
412                 }
413
414                 // sets the explicit path to store local user.config or equivalent.
415                 // (returns the path validity.)
416                 public static bool SetUserLocalPath (string configPath)
417                 {
418                         if (CheckPath (configPath))
419                         {
420                                 userLocalBasePath = configPath;
421                                 return true;
422                         }
423                         else
424                                 return false;
425                 }
426
427                 private static bool CheckFileName (string configFile)
428                 {
429                         /*
430                         char[] invalidFileChars = Path.GetInvalidFileNameChars();
431
432                         foreach (char invalidChar in invalidFileChars)
433                         {
434                                 if (configFile.Contains(invalidChar.ToString()))
435                                 {
436                                         return false;
437                                 }
438                         }
439                         return true;
440                         */
441                         return configFile.IndexOfAny (Path.GetInvalidFileNameChars ()) < 0;
442                 }
443
444                 // sets the explicit roaming file name which is user.config equivalent.
445                 // (returns the file name validity.)
446                 public static bool SetUserRoamingFileName (string configFile)
447                 {
448                         if (CheckFileName (configFile))
449                         {
450                                 userRoamingName = configFile;
451                                 return true;
452                         }
453                         else
454                                 return false;
455                 }
456
457                 // sets the explicit local file name which is user.config equivalent.
458                 // (returns the file name validity.)
459                 public static bool SetUserLocalFileName (string configFile)
460                 {
461                         if (CheckFileName (configFile))
462                         {
463                                 userLocalName = configFile;
464                                 return true;
465                         }
466                         else
467                                 return false;
468                 }
469
470                 // sets the explicit company name for folder.
471                 // (returns the file name validity.)
472                 public static bool SetCompanyName (string companyName)
473                 {
474                         if (CheckFileName (companyName))
475                         {
476                                 CompanyName = companyName;
477                                 return true;
478                         }
479                         else
480                                 return false;
481                 }
482
483                 // sets the explicit product name for folder.
484                 // (returns the file name validity.)
485                 public static bool SetProductName (string productName)
486                 {
487                         if (CheckFileName (productName))
488                         {
489                                 ProductName = productName;
490                                 return true;
491                         }
492                         else
493                                 return false;
494                 }
495
496                 // sets the explicit major version for folder.
497                 public static bool SetVersion (int major)
498                 {
499                         ForceVersion = string.Format ("{0}", major);
500                         return true;
501                 }
502
503                 // sets the explicit major and minor versions for folder.
504                 public static bool SetVersion (int major, int minor)
505                 {
506                         ForceVersion = string.Format ("{0}.{1}", major, minor);
507                         return true;
508                 }
509
510                 // sets the explicit major/minor/build numbers for folder.
511                 public static bool SetVersion (int major, int minor, int build)
512                 {
513                         ForceVersion = string.Format ("{0}.{1}.{2}", major, minor, build);
514                         return true;
515                 }
516
517                 // sets the explicit major/minor/build/revision numbers for folder.
518                 public static bool SetVersion (int major, int minor, int build, int revision)
519                 {
520                         ForceVersion = string.Format ("{0}.{1}.{2}.{3}", major, minor, build, revision);
521                         return true;
522                 }
523
524                 // sets the explicit version number string for folder.
525                 public static bool SetVersion (string forceVersion)
526                 {
527                         if (CheckFileName (forceVersion))
528                         {
529                                 ForceVersion = forceVersion;
530                                 return true;
531                         }
532                         else
533                                 return false;
534                 }
535
536                 private static bool CheckPath (string configPath)
537                 {
538                         char[] invalidPathChars = Path.GetInvalidPathChars ();
539
540                         /*
541                         foreach (char invalidChar in invalidPathChars)
542                         {
543                                 if (configPath.Contains (invalidChar.ToString()))
544                                 {
545                                         return false;
546                                 }
547                         }
548                         */
549                         if (configPath.IndexOfAny (invalidPathChars) >= 0)
550                                 return false;
551
552                         string folder = configPath;
553                         string fileName;
554                         while ((fileName = Path.GetFileName (folder)) != "")
555                         {
556                                 if (!CheckFileName (fileName))
557                                 {
558                                         return false;
559                                 }
560                                 folder = Path.GetDirectoryName (folder);
561                         }
562
563                         return true;
564                 }
565
566
567                 public override string Name {
568                         get { return base.Name; }
569                 }
570
571                 string app_name = String.Empty;//"OJK.CustomSetting.CustomizableLocalFileSettingsProvider";
572                 public override string ApplicationName {
573                         get { return app_name; }
574                         set { app_name = value; }
575                 }
576
577                 private ExeConfigurationFileMap exeMapCurrent = null;
578                 private ExeConfigurationFileMap exeMapPrev = null;
579                 private SettingsPropertyValueCollection values = null;
580
581                 /// <remarks>
582                 /// Hack to remove the XmlDeclaration that the XmlSerializer adds.
583                 /// <br />
584                 /// see <a href="https://github.com/mono/mono/pull/2273">Issue 2273</a> for details
585                 /// </remarks>
586                 private string StripXmlHeader (string serializedValue)
587                 {
588                         if (serializedValue == null)
589                         {
590                                 return string.Empty;
591                         }
592
593                         XmlDocument doc = new XmlDocument ();
594                         XmlElement valueXml = doc.CreateElement ("value");
595                         valueXml.InnerXml = serializedValue;
596
597                         foreach (XmlNode child in valueXml.ChildNodes) {
598                                 if (child.NodeType == XmlNodeType.XmlDeclaration) {
599                                         valueXml.RemoveChild (child);
600                                         break;
601                                 }
602                         }
603
604                         // InnerXml will give you well-formed XML that you could save as a separate document, and 
605                         // InnerText will immediately give you a pure-text representation of this inner XML.
606                         return valueXml.InnerXml;
607                 }
608
609                 private void SaveProperties (ExeConfigurationFileMap exeMap, SettingsPropertyValueCollection collection, ConfigurationUserLevel level, SettingsContext context, bool checkUserLevel)
610                 {
611                         Configuration config = ConfigurationManager.OpenMappedExeConfiguration (exeMap, level);
612                         
613                         UserSettingsGroup userGroup = config.GetSectionGroup ("userSettings") as UserSettingsGroup;
614                         bool isRoaming = (level == ConfigurationUserLevel.PerUserRoaming);
615
616                         if (userGroup == null) {
617                                 userGroup = new UserSettingsGroup ();
618                                 config.SectionGroups.Add ("userSettings", userGroup);
619                         }
620                         ApplicationSettingsBase asb = context.CurrentSettings;
621                         string class_name = NormalizeInvalidXmlChars ((asb != null ? asb.GetType () : typeof (ApplicationSettingsBase)).FullName);
622                         ClientSettingsSection userSection = null;
623                         ConfigurationSection cnf = userGroup.Sections.Get (class_name);
624                         userSection = cnf as ClientSettingsSection;
625                         if (userSection == null) {
626                                 userSection = new ClientSettingsSection ();
627                                 userGroup.Sections.Add (class_name, userSection);
628                         }
629
630                         bool hasChanges = false;
631
632                         if (userSection == null)
633                                 return;
634
635                         foreach (SettingsPropertyValue value in collection) {
636                                 if (checkUserLevel && value.Property.Attributes.Contains (typeof (SettingsManageabilityAttribute)) != isRoaming)
637                                         continue;
638                                 // The default impl does not save the ApplicationScopedSetting properties
639                                 if (value.Property.Attributes.Contains (typeof (ApplicationScopedSettingAttribute)))
640                                         continue;
641
642                                 hasChanges = true;
643                                 SettingElement element = userSection.Settings.Get (value.Name);
644                                 if (element == null) {
645                                         element = new SettingElement (value.Name, value.Property.SerializeAs);
646                                         userSection.Settings.Add (element);
647                                 }
648                                 if (element.Value.ValueXml == null)
649                                         element.Value.ValueXml = new XmlDocument ().CreateElement ("value");
650                                 switch (value.Property.SerializeAs) {
651                                 case SettingsSerializeAs.Xml:
652                                         element.Value.ValueXml.InnerXml = StripXmlHeader (value.SerializedValue as string);
653                                         break;
654                                 case SettingsSerializeAs.String:
655                                         element.Value.ValueXml.InnerText = value.SerializedValue as string;
656                                         break;
657                                 case SettingsSerializeAs.Binary:
658                                         element.Value.ValueXml.InnerText = value.SerializedValue != null ? Convert.ToBase64String (value.SerializedValue as byte []) : string.Empty;
659                                         break;
660                                 default:
661                                         throw new NotImplementedException ();
662                                 }
663                         }
664                         if (hasChanges)
665                                 config.Save (ConfigurationSaveMode.Minimal, true);
666                 }
667
668                 // NOTE: We should add here all the chars that are valid in a name of a class (Ecma-wise),
669                 // but invalid in an xml element name, and provide a better impl if we get too many of them.
670                 string NormalizeInvalidXmlChars (string str)
671                 {
672                         char [] invalid_chars = new char [] { '+' };
673
674                         if (str == null || str.IndexOfAny (invalid_chars) == -1)
675                                 return str;
676
677                         // Replace with its hexadecimal values.
678                         str = str.Replace ("+", "_x002B_");
679                         return str;
680                 }
681
682                 private void LoadPropertyValue (SettingsPropertyCollection collection, SettingElement element, bool allowOverwrite)
683                 {
684                         SettingsProperty prop = collection [element.Name];
685                         if (prop == null) { // see bug #343459
686                                 prop = new SettingsProperty (element.Name);
687                                 collection.Add (prop);
688                         }
689
690                         SettingsPropertyValue value = new SettingsPropertyValue (prop);
691                         value.IsDirty = false;
692                         if (element.Value.ValueXml != null) {
693                                 switch (value.Property.SerializeAs) {
694                                 case SettingsSerializeAs.Xml:
695                                         value.SerializedValue = element.Value.ValueXml.InnerXml;
696                                         break;
697                                 case SettingsSerializeAs.String:
698                                         value.SerializedValue = element.Value.ValueXml.InnerText.Trim ();
699                                         break;
700                                 case SettingsSerializeAs.Binary:
701                                         value.SerializedValue = Convert.FromBase64String (element.Value.ValueXml.InnerText);
702                                         break;
703                                 }
704                         }
705                         else
706                                 value.SerializedValue = prop.DefaultValue;
707                         try
708                         {
709                                 if (allowOverwrite)
710                                         values.Remove (element.Name);
711                                 values.Add (value);
712                         } catch (ArgumentException ex) {
713                                 throw new ConfigurationErrorsException (string.Format (
714                                         CultureInfo.InvariantCulture,
715                                         "Failed to load value for '{0}'.",
716                                         element.Name), ex);
717                         }
718                 }
719
720                 private void LoadProperties (ExeConfigurationFileMap exeMap, SettingsPropertyCollection collection, ConfigurationUserLevel level, string sectionGroupName, bool allowOverwrite, string groupName)
721                 {
722                         Configuration config = ConfigurationManager.OpenMappedExeConfiguration (exeMap,level);
723                         
724                         ConfigurationSectionGroup sectionGroup = config.GetSectionGroup (sectionGroupName);
725                         if (sectionGroup != null) {
726                                 foreach (ConfigurationSection configSection in sectionGroup.Sections) {
727                                         if (configSection.SectionInformation.Name != groupName)
728                                                 continue;
729
730                                         ClientSettingsSection clientSection = configSection as ClientSettingsSection;
731                                         if (clientSection == null)
732                                                 continue;
733
734                                         foreach (SettingElement element in clientSection.Settings) {
735                                                 LoadPropertyValue(collection, element, allowOverwrite);
736                                         }
737                                         // Only the first one seems to be processed by MS
738                                         break;
739                                 }
740                         }
741
742                 }
743
744                 public override void SetPropertyValues (SettingsContext context, SettingsPropertyValueCollection collection)
745                 {
746                         CreateExeMap ();
747
748                         if (UserLocalFullPath == UserRoamingFullPath)
749                         {
750                                 SaveProperties (exeMapCurrent, collection, ConfigurationUserLevel.PerUserRoaming, context, false);
751                         } else {
752                                 SaveProperties (exeMapCurrent, collection, ConfigurationUserLevel.PerUserRoaming, context, true);
753                                 SaveProperties (exeMapCurrent, collection, ConfigurationUserLevel.PerUserRoamingAndLocal, context, true);
754                         }
755                 }
756
757                 public override SettingsPropertyValueCollection GetPropertyValues (SettingsContext context, SettingsPropertyCollection collection)
758                 {
759                         CreateExeMap ();
760
761                         values = new SettingsPropertyValueCollection ();
762                         string groupName = context ["GroupName"] as string;
763                         groupName = NormalizeInvalidXmlChars (groupName); // we likely saved the element removing the non valid xml chars.
764                         LoadProperties (exeMapCurrent, collection, ConfigurationUserLevel.None, "applicationSettings", false, groupName);
765                         LoadProperties (exeMapCurrent, collection, ConfigurationUserLevel.None, "userSettings", false, groupName);
766
767                         LoadProperties (exeMapCurrent, collection, ConfigurationUserLevel.PerUserRoaming, "userSettings", true, groupName);
768                         LoadProperties (exeMapCurrent, collection, ConfigurationUserLevel.PerUserRoamingAndLocal, "userSettings", true, groupName);
769
770                         // create default values if not exist
771                         foreach (SettingsProperty p in collection)
772                                 if (values [p.Name] == null)
773                                         values.Add (new SettingsPropertyValue (p));
774                         return values;
775                 }
776
777                 /// creates an ExeConfigurationFileMap
778                 private void CreateExeMap ()
779                 {
780                         if (exeMapCurrent == null) {
781                                 CreateUserConfigPath ();
782
783                                 // current version
784                                 exeMapCurrent = new ExeConfigurationFileMap ();
785                                 
786                                 // exeMapCurrent.ExeConfigFilename = System.Windows.Forms.Application.ExecutablePath + ".config";
787                                 Assembly entry = Assembly.GetEntryAssembly () ?? Assembly.GetExecutingAssembly ();
788                                 exeMapCurrent.ExeConfigFilename = entry.Location + ".config";
789                                 exeMapCurrent.LocalUserConfigFilename = UserLocalFullPath;
790                                 exeMapCurrent.RoamingUserConfigFilename = UserRoamingFullPath;
791
792                                 if (webConfigurationFileMapType != null && typeof (ConfigurationFileMap).IsAssignableFrom (webConfigurationFileMapType)) {
793                                         try {
794                                                 ConfigurationFileMap cfgFileMap = Activator.CreateInstance (webConfigurationFileMapType) as ConfigurationFileMap;
795                                                 if (cfgFileMap != null) {
796                                                         string fpath = cfgFileMap.MachineConfigFilename;
797                                                         if (!String.IsNullOrEmpty (fpath))
798                                                                 exeMapCurrent.ExeConfigFilename = fpath;
799                                                 }
800                                         } catch {
801                                                 // ignore
802                                         }
803                                 }
804                                 
805                                 // previous version
806                                 if ((PrevUserLocalFullPath != "") && (PrevUserRoamingFullPath != ""))
807                                 {
808                                         exeMapPrev = new ExeConfigurationFileMap();
809                                         // exeMapPrev.ExeConfigFilename = System.Windows.Forms.Application.ExecutablePath + ".config";
810                                         exeMapPrev.ExeConfigFilename = entry.Location + ".config";
811                                         exeMapPrev.LocalUserConfigFilename = PrevUserLocalFullPath;
812                                         exeMapPrev.RoamingUserConfigFilename = PrevUserRoamingFullPath;
813                                 }
814                         }
815                 }
816
817                 // FIXME: implement
818                 public SettingsPropertyValue GetPreviousVersion (SettingsContext context, SettingsProperty property)
819                 {
820                         return null;
821                 }
822
823                 public void Reset (SettingsContext context)
824                 {
825                         if (values != null) {
826                                 foreach (SettingsPropertyValue propertyValue in values) {
827                                         // Can't use propertyValue.Property.DefaultValue
828                                         // as it may cause InvalidCastException (see bug# 532180)
829                                         values[propertyValue.Name].PropertyValue = propertyValue.Reset ();
830                                 }
831                         }
832                 }
833
834                 // FIXME: implement
835                 public void Upgrade (SettingsContext context, SettingsPropertyCollection properties)
836                 {
837                 }
838
839                 public static void setCreate ()
840                 {
841                         CreateUserConfigPath();
842                 }
843         }
844 }
845
846 #endif