The Labs \ Source Viewer \ SSCLI \ System.Configuration \ XmlEscaper

  1. //------------------------------------------------------------------------------
  2. // <copyright file="LocalFileSettingsProvider.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System.Diagnostics.CodeAnalysis;
  16. namespace System.Configuration
  17. {
  18.     using System;
  19.     using System.Collections;
  20.     using System.Collections.Specialized;
  21.     using System.ComponentModel;
  22.     using System.Configuration;
  23.     using System.Configuration.Provider;
  24.     using System.Diagnostics;
  25.     using System.Globalization;
  26.     using System.IO;
  27.     using System.Security;
  28.     using System.Security.Permissions;
  29.     using System.Xml;
  30.     using System.Xml.Serialization;
  31.     using System.Runtime.Versioning;
  32.    
  33.     /// <devdoc>
  34.     /// <para>
  35.     /// This is a provider used to store configuration settings locally for client applications.
  36.     /// </para>
  37.     /// </devdoc>
  38.     [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"), PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
  39.     public class LocalFileSettingsProvider : SettingsProvider, IApplicationSettingsProvider
  40.     {
  41.         private string _appName = String.Empty;
  42.         private ClientSettingsStore _store = null;
  43.         private string _prevLocalConfigFileName = null;
  44.         private string _prevRoamingConfigFileName = null;
  45.         private XmlEscaper _escaper = null;
  46.        
  47.         /// <devdoc>
  48.         /// Abstract SettingsProvider property.
  49.         /// </devdoc>
  50.         public override string ApplicationName {
  51.             get { return _appName; }
  52.             set { _appName = value; }
  53.         }
  54.        
  55.         private XmlEscaper Escaper {
  56.             get {
  57.                 if (_escaper == null) {
  58.                     _escaper = new XmlEscaper();
  59.                 }
  60.                
  61.                 return _escaper;
  62.             }
  63.         }
  64.        
  65.         /// <devdoc>
  66.         /// We maintain a single instance of the ClientSettingsStore per instance of provider.
  67.         /// </devdoc>
  68.         private ClientSettingsStore Store {
  69.             get {
  70.                 if (_store == null) {
  71.                     _store = new ClientSettingsStore();
  72.                 }
  73.                
  74.                 return _store;
  75.             }
  76.         }
  77.        
  78.         /// <devdoc>
  79.         /// Abstract ProviderBase method.
  80.         /// </devdoc>
  81.         public override void Initialize(string name, NameValueCollection values)
  82.         {
  83.             if (String.IsNullOrEmpty(name)) {
  84.                 name = "LocalFileSettingsProvider";
  85.             }
  86.            
  87.             base.Initialize(name, values);
  88.         }
  89.        
  90.         /// <devdoc>
  91.         /// Abstract SettingsProvider method
  92.         /// </devdoc>
  93.         public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection properties)
  94.         {
  95.             SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
  96.             string sectionName = GetSectionName(context);
  97.            
  98.             //<--Look for this section in both applicationSettingsGroup and userSettingsGroup-->
  99.             IDictionary appSettings = Store.ReadSettings(sectionName, false);
  100.             IDictionary userSettings = Store.ReadSettings(sectionName, true);
  101.             ConnectionStringSettingsCollection connStrings = Store.ReadConnectionStrings();
  102.            
  103.             //<--Now map each SettingProperty to the right StoredSetting and deserialize the value if found.-->
  104.             foreach (SettingsProperty setting in properties) {
  105.                 string settingName = setting.Name;
  106.                 SettingsPropertyValue value = new SettingsPropertyValue(setting);
  107.                
  108.                 // First look for and handle "special" settings
  109.                 SpecialSettingAttribute attr = setting.Attributes[typeof(SpecialSettingAttribute)] as SpecialSettingAttribute;
  110.                 bool isConnString = (attr != null) ? (attr.SpecialSetting == SpecialSetting.ConnectionString) : false;
  111.                
  112.                 if (isConnString) {
  113.                     string connStringName = sectionName + "." + settingName;
  114.                     if (connStrings != null && connStrings[connStringName] != null) {
  115.                         value.PropertyValue = connStrings[connStringName].ConnectionString;
  116.                     }
  117.                     else if (setting.DefaultValue != null && setting.DefaultValue is string) {
  118.                         value.PropertyValue = setting.DefaultValue;
  119.                     }
  120.                     else {
  121.                         //No value found and no default specified
  122.                         value.PropertyValue = String.Empty;
  123.                     }
  124.                    
  125.                     value.IsDirty = false;
  126.                     //reset IsDirty so that it is correct when SetPropertyValues is called
  127.                     values.Add(value);
  128.                     continue;
  129.                 }
  130.                
  131.                 // Not a "special" setting
  132.                 bool isUserSetting = IsUserSetting(setting);
  133.                
  134.                 if (isUserSetting && !ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) {
  135.                     // We encountered a user setting, but the current configuration system does not support
  136.                     // user settings.
  137.                     throw new ConfigurationErrorsException(SR.GetString(SR.UserSettingsNotSupported));
  138.                 }
  139.                
  140.                 IDictionary settings = isUserSetting ? userSettings : appSettings;
  141.                
  142.                 if (settings.Contains(settingName)) {
  143.                     StoredSetting ss = (StoredSetting)settings[settingName];
  144.                     string valueString = ss.Value.InnerXml;
  145.                    
  146.                     // We need to un-escape string serialized values
  147.                     if (ss.SerializeAs == SettingsSerializeAs.String) {
  148.                         valueString = Escaper.Unescape(valueString);
  149.                     }
  150.                    
  151.                     value.SerializedValue = valueString;
  152.                 }
  153.                 else if (setting.DefaultValue != null) {
  154.                     value.SerializedValue = setting.DefaultValue;
  155.                 }
  156.                 else {
  157.                     //No value found and no default specified
  158.                     value.PropertyValue = null;
  159.                 }
  160.                
  161.                 value.IsDirty = false;
  162.                 //reset IsDirty so that it is correct when SetPropertyValues is called
  163.                 values.Add(value);
  164.             }
  165.            
  166.             return values;
  167.         }
  168.        
  169.         /// <devdoc>
  170.         /// Abstract SettingsProvider method
  171.         /// </devdoc>
  172.         public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection values)
  173.         {
  174.             string sectionName = GetSectionName(context);
  175.             IDictionary roamingUserSettings = new Hashtable();
  176.             IDictionary localUserSettings = new Hashtable();
  177.            
  178.             foreach (SettingsPropertyValue value in values) {
  179.                 SettingsProperty setting = value.Property;
  180.                 bool isUserSetting = IsUserSetting(setting);
  181.                
  182.                 if (value.IsDirty) {
  183.                     if (isUserSetting) {
  184.                         bool isRoaming = IsRoamingSetting(setting);
  185.                         StoredSetting ss = new StoredSetting(setting.SerializeAs, SerializeToXmlElement(setting, value));
  186.                        
  187.                         if (isRoaming) {
  188.                             roamingUserSettings[setting.Name] = ss;
  189.                         }
  190.                         else {
  191.                             localUserSettings[setting.Name] = ss;
  192.                         }
  193.                        
  194.                         value.IsDirty = false;
  195.                         //reset IsDirty
  196.                     }
  197.                     else {
  198.                         // This is an app-scoped or connection string setting that has been written to.
  199.                         // We don't support saving these.
  200.                     }
  201.                 }
  202.             }
  203.            
  204.             if (roamingUserSettings.Count > 0) {
  205.                 Store.WriteSettings(sectionName, true, roamingUserSettings);
  206.             }
  207.            
  208.             if (localUserSettings.Count > 0) {
  209.                 Store.WriteSettings(sectionName, false, localUserSettings);
  210.             }
  211.         }
  212.        
  213.         /// <devdoc>
  214.         /// Implementation of IClientSettingsProvider.Reset. Resets user scoped settings to the values
  215.         /// in app.exe.config, does nothing for app scoped settings.
  216.         /// </devdoc>
  217.         public void Reset(SettingsContext context)
  218.         {
  219.             string sectionName = GetSectionName(context);
  220.            
  221.             // First revert roaming, then local
  222.             Store.RevertToParent(sectionName, true);
  223.             Store.RevertToParent(sectionName, false);
  224.         }
  225.        
  226.         /// <devdoc>
  227.         /// Implementation of IClientSettingsProvider.Upgrade.
  228.         /// Tries to locate a previous version of the user.config file. If found, it migrates matching settings.
  229.         /// If not, it does nothing.
  230.         /// </devdoc>
  231.         public void Upgrade(SettingsContext context, SettingsPropertyCollection properties)
  232.         {
  233.             // Separate the local and roaming settings and upgrade them separately.
  234.            
  235.             SettingsPropertyCollection local = new SettingsPropertyCollection();
  236.             SettingsPropertyCollection roaming = new SettingsPropertyCollection();
  237.            
  238.             foreach (SettingsProperty sp in properties) {
  239.                 bool isRoaming = IsRoamingSetting(sp);
  240.                
  241.                 if (isRoaming) {
  242.                     roaming.Add(sp);
  243.                 }
  244.                 else {
  245.                     local.Add(sp);
  246.                 }
  247.             }
  248.            
  249.             if (roaming.Count > 0) {
  250.                 Upgrade(context, roaming, true);
  251.             }
  252.            
  253.             if (local.Count > 0) {
  254.                 Upgrade(context, local, false);
  255.             }
  256.         }
  257.        
  258.         /// <devdoc>
  259.         /// Encapsulates the Version constructor so that we can return null when an exception is thrown.
  260.         /// </devdoc>
  261.         private Version CreateVersion(string name)
  262.         {
  263.             Version ver = null;
  264.            
  265.             try {
  266.                 ver = new Version(name);
  267.             }
  268.             catch (ArgumentException) {
  269.                 ver = null;
  270.             }
  271.             catch (OverflowException) {
  272.                 ver = null;
  273.             }
  274.             catch (FormatException) {
  275.                 ver = null;
  276.             }
  277.            
  278.             return ver;
  279.         }
  280.        
  281.         /// <devdoc>
  282.         /// Implementation of IClientSettingsProvider.GetPreviousVersion.
  283.         /// </devdoc>
  284.         // Security Note: Like Upgrade, GetPreviousVersion involves finding a previous version user.config file and
  285.         // reading settings from it. To support this in partial trust, we need to assert file i/o here. We believe
  286.         // this to be safe, since the user does not have a way to specify the file or control where we look for it.
  287.         // So it is no different than reading from the default user.config file, which we already allow in partial trust.
  288.         // BTW, the Link/Inheritance demand pair here is just a copy of what's at the class level, and is needed since
  289.         // we are overriding security at method level.
  290.         [FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read), PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"), PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
  291.         public SettingsPropertyValue GetPreviousVersion(SettingsContext context, SettingsProperty property)
  292.         {
  293.             bool isRoaming = IsRoamingSetting(property);
  294.             string prevConfig = GetPreviousConfigFileName(isRoaming);
  295.            
  296.             if (!String.IsNullOrEmpty(prevConfig)) {
  297.                 SettingsPropertyCollection properties = new SettingsPropertyCollection();
  298.                 properties.Add(property);
  299.                 SettingsPropertyValueCollection values = GetSettingValuesFromFile(prevConfig, GetSectionName(context), true, properties);
  300.                 return values[property.Name];
  301.             }
  302.             else {
  303.                 SettingsPropertyValue value = new SettingsPropertyValue(property);
  304.                 value.PropertyValue = null;
  305.                 return value;
  306.             }
  307.         }
  308.        
  309.         /// <devdoc>
  310.         /// Locates the previous version of user.config, if present. The previous version is determined
  311.         /// by walking up one directory level in the *UserConfigPath and searching for the highest version
  312.         /// number less than the current version.
  313.         /// SECURITY NOTE: Config path information is privileged - do not directly pass this on to untrusted callers.
  314.         /// Note this is meant to be used at installation time to help migrate
  315.         /// config settings from a previous version of the app.
  316.         /// </devdoc>
  317.         [ResourceExposure(ResourceScope.None)]
  318.         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
  319.         private string GetPreviousConfigFileName(bool isRoaming)
  320.         {
  321.             if (!ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) {
  322.                 throw new ConfigurationErrorsException(SR.GetString(SR.UserSettingsNotSupported));
  323.             }
  324.            
  325.             string prevConfigFile = isRoaming ? _prevRoamingConfigFileName : _prevLocalConfigFileName;
  326.            
  327.             if (String.IsNullOrEmpty(prevConfigFile)) {
  328.                 string userConfigPath = isRoaming ? ConfigurationManagerInternalFactory.Instance.ExeRoamingConfigDirectory : ConfigurationManagerInternalFactory.Instance.ExeLocalConfigDirectory;
  329.                 Version curVer = CreateVersion(ConfigurationManagerInternalFactory.Instance.ExeProductVersion);
  330.                 Version prevVer = null;
  331.                 DirectoryInfo prevDir = null;
  332.                 string file = null;
  333.                
  334.                 if (curVer == null) {
  335.                     return null;
  336.                 }
  337.                
  338.                 DirectoryInfo parentDir = Directory.GetParent(userConfigPath);
  339.                
  340.                 if (parentDir.Exists) {
  341.                     foreach (DirectoryInfo dir in parentDir.GetDirectories()) {
  342.                         Version tempVer = CreateVersion(dir.Name);
  343.                        
  344.                         if (tempVer != null && tempVer < curVer) {
  345.                             if (prevVer == null) {
  346.                                 prevVer = tempVer;
  347.                                 prevDir = dir;
  348.                             }
  349.                             else if (tempVer > prevVer) {
  350.                                 prevVer = tempVer;
  351.                                 prevDir = dir;
  352.                             }
  353.                         }
  354.                     }
  355.                    
  356.                     if (prevDir != null) {
  357.                         file = Path.Combine(prevDir.FullName, ConfigurationManagerInternalFactory.Instance.UserConfigFilename);
  358.                     }
  359.                    
  360.                     if (File.Exists(file)) {
  361.                         prevConfigFile = file;
  362.                     }
  363.                 }
  364.                
  365.                 //Cache for future use.
  366.                 if (isRoaming) {
  367.                     _prevRoamingConfigFileName = prevConfigFile;
  368.                 }
  369.                 else {
  370.                     _prevLocalConfigFileName = prevConfigFile;
  371.                 }
  372.             }
  373.            
  374.             return prevConfigFile;
  375.         }
  376.        
  377.         /// <devdoc>
  378.         /// Gleans information from the SettingsContext and determines the name of the config section.
  379.         /// </devdoc>
  380.         private string GetSectionName(SettingsContext context)
  381.         {
  382.             string groupName = (string)context["GroupName"];
  383.             string key = (string)context["SettingsKey"];
  384.            
  385.             Debug.Assert(groupName != null, "SettingsContext did not have a GroupName!");
  386.            
  387.             string sectionName = groupName;
  388.            
  389.             if (!String.IsNullOrEmpty(key)) {
  390.                 sectionName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", sectionName, key);
  391.             }
  392.            
  393.             return XmlConvert.EncodeLocalName(sectionName);
  394.         }
  395.        
  396.         /// <devdoc>
  397.         /// Retrieves the values of settings from the given config file (as opposed to using
  398.         /// the configuration for the current context)
  399.         /// </devdoc>
  400.         private SettingsPropertyValueCollection GetSettingValuesFromFile(string configFileName, string sectionName, bool userScoped, SettingsPropertyCollection properties)
  401.         {
  402.             SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
  403.             IDictionary settings = ClientSettingsStore.ReadSettingsFromFile(configFileName, sectionName, userScoped);
  404.            
  405.             // Map each SettingProperty to the right StoredSetting and deserialize the value if found.
  406.             foreach (SettingsProperty setting in properties) {
  407.                 string settingName = setting.Name;
  408.                 SettingsPropertyValue value = new SettingsPropertyValue(setting);
  409.                
  410.                 if (settings.Contains(settingName)) {
  411.                     StoredSetting ss = (StoredSetting)settings[settingName];
  412.                     string valueString = ss.Value.InnerXml;
  413.                    
  414.                     // We need to un-escape string serialized values
  415.                     if (ss.SerializeAs == SettingsSerializeAs.String) {
  416.                         valueString = Escaper.Unescape(valueString);
  417.                     }
  418.                    
  419.                     value.SerializedValue = valueString;
  420.                     value.IsDirty = true;
  421.                     values.Add(value);
  422.                 }
  423.             }
  424.            
  425.             return values;
  426.         }
  427.        
  428.         /// <devdoc>
  429.         /// Indicates whether a setting is roaming or not.
  430.         /// </devdoc>
  431.         private static bool IsRoamingSetting(SettingsProperty setting)
  432.         {
  433.             return false;
  434.         }
  435.        
  436.         /// <devdoc>
  437.         /// This provider needs settings to be marked with either the UserScopedSettingAttribute or the
  438.         /// ApplicationScopedSettingAttribute. This method determines whether this setting is user-scoped
  439.         /// or not. It will throw if none or both of the attributes are present.
  440.         /// </devdoc>
  441.         private bool IsUserSetting(SettingsProperty setting)
  442.         {
  443.             bool isUser = setting.Attributes[typeof(UserScopedSettingAttribute)] is UserScopedSettingAttribute;
  444.             bool isApp = setting.Attributes[typeof(ApplicationScopedSettingAttribute)] is ApplicationScopedSettingAttribute;
  445.            
  446.             if (isUser && isApp) {
  447.                 throw new ConfigurationErrorsException(SR.GetString(SR.BothScopeAttributes));
  448.             }
  449.             else if (!(isUser || isApp)) {
  450.                 throw new ConfigurationErrorsException(SR.GetString(SR.NoScopeAttributes));
  451.             }
  452.            
  453.             return isUser;
  454.         }
  455.        
  456.         private XmlNode SerializeToXmlElement(SettingsProperty setting, SettingsPropertyValue value)
  457.         {
  458.             XmlDocument doc = new XmlDocument();
  459.             XmlElement valueXml = doc.CreateElement("value");
  460.            
  461.             string serializedValue = value.SerializedValue as string;
  462.            
  463.             if (serializedValue == null && setting.SerializeAs == SettingsSerializeAs.Binary) {
  464.                 // SettingsPropertyValue returns a byte[] in the binary serialization case. We need to
  465.                 // encode this - we use base64 since SettingsPropertyValue understands it and we won't have
  466.                 // to special case while deserializing.
  467.                 byte[] buf = value.SerializedValue as byte[];
  468.                 if (buf != null) {
  469.                     serializedValue = Convert.ToBase64String(buf);
  470.                 }
  471.             }
  472.            
  473.             if (serializedValue == null) {
  474.                 serializedValue = String.Empty;
  475.             }
  476.            
  477.             // We need to escape string serialized values
  478.             if (setting.SerializeAs == SettingsSerializeAs.String) {
  479.                 serializedValue = Escaper.Escape(serializedValue);
  480.             }
  481.            
  482.             valueXml.InnerXml = serializedValue;
  483.            
  484.             XmlNode unwanted = null;
  485.             foreach (XmlNode child in valueXml.ChildNodes) {
  486.                 if (child.NodeType == XmlNodeType.XmlDeclaration) {
  487.                     unwanted = child;
  488.                     break;
  489.                 }
  490.             }
  491.             if (unwanted != null) {
  492.                 valueXml.RemoveChild(unwanted);
  493.             }
  494.            
  495.             return valueXml;
  496.         }
  497.        
  498.         /// <devdoc>
  499.         /// Private version of upgrade that uses isRoaming to determine which config file to use.
  500.         /// </devdoc>
  501.         // Security Note: Upgrade involves finding a previous version user.config file and reading settings from it. To
  502.         // support this in partial trust, we need to assert file i/o here. We believe this to be safe, since the user
  503.         // does not have a way to specify the file or control where we look for it. As such, it is no different than
  504.         // reading from the default user.config file, which we already allow in partial trust.
  505.         // The following suppress is okay, since the Link/Inheritance demand pair at the class level are not needed for
  506.         // this method, since it is private.
  507.         [SuppressMessage("Microsoft.Security", "CA2114:MethodSecurityShouldBeASupersetOfType")]
  508.         [FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read)]
  509.         private void Upgrade(SettingsContext context, SettingsPropertyCollection properties, bool isRoaming)
  510.         {
  511.             string prevConfig = GetPreviousConfigFileName(isRoaming);
  512.            
  513.             if (!String.IsNullOrEmpty(prevConfig)) {
  514.                 //Filter the settings properties to exclude those that have a NoSettingsVersionUpgradeAttribute on them.
  515.                 SettingsPropertyCollection upgradeProperties = new SettingsPropertyCollection();
  516.                 foreach (SettingsProperty sp in properties) {
  517.                     if (!(sp.Attributes[typeof(NoSettingsVersionUpgradeAttribute)] is NoSettingsVersionUpgradeAttribute)) {
  518.                         upgradeProperties.Add(sp);
  519.                     }
  520.                 }
  521.                
  522.                 SettingsPropertyValueCollection values = GetSettingValuesFromFile(prevConfig, GetSectionName(context), true, upgradeProperties);
  523.                 SetPropertyValues(context, values);
  524.             }
  525.         }
  526.        
  527.         private class XmlEscaper
  528.         {
  529.             private XmlDocument doc;
  530.             private XmlElement temp;
  531.            
  532.             internal XmlEscaper()
  533.             {
  534.                 doc = new XmlDocument();
  535.                 temp = doc.CreateElement("temp");
  536.             }
  537.            
  538.             internal string Escape(string xmlString)
  539.             {
  540.                 if (String.IsNullOrEmpty(xmlString)) {
  541.                     return xmlString;
  542.                 }
  543.                
  544.                 temp.InnerText = xmlString;
  545.                 return temp.InnerXml;
  546.             }
  547.            
  548.             internal string Unescape(string escapedString)
  549.             {
  550.                 if (String.IsNullOrEmpty(escapedString)) {
  551.                     return escapedString;
  552.                 }
  553.                
  554.                 temp.InnerXml = escapedString;
  555.                 return temp.InnerText;
  556.             }
  557.         }
  558.     }
  559. }

Developer Fusion