//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope="member", Target="System.Configuration.ClientSettingsStore+QuotaEnforcedStream.Dispose(System.Boolean):System.Void")] namespace System.Configuration { using System; using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Configuration.Internal; using System.Configuration.Provider; using System.Diagnostics; using System.IO; using System.Security; using System.Security.Permissions; using System.Xml; /// /// This class abstracts the details of config system away from the LocalFileSettingsProvider. It talks to /// the configuration API and the relevant Sections to read and write settings. /// It understands sections of type ClientSettingsSection. /// /// NOTE: This API supports reading from app.exe.config and user.config, but writing only to /// user.config. /// internal sealed class ClientSettingsStore { private const string ApplicationSettingsGroupName = "applicationSettings"; private const string UserSettingsGroupName = "userSettings"; private const string ApplicationSettingsGroupPrefix = ApplicationSettingsGroupName + "/"; private const string UserSettingsGroupPrefix = UserSettingsGroupName + "/"; private Configuration GetUserConfig(bool isRoaming) { ConfigurationUserLevel userLevel = isRoaming ? ConfigurationUserLevel.PerUserRoaming : ConfigurationUserLevel.PerUserRoamingAndLocal; return ClientSettingsConfigurationHost.OpenExeConfiguration(userLevel); } private ClientSettingsSection GetConfigSection(Configuration config, string sectionName, bool declare) { string fullSectionName = UserSettingsGroupPrefix + sectionName; ClientSettingsSection section = null; if (config != null) { section = config.GetSection(fullSectionName) as ClientSettingsSection; if (section == null && declare) { // Looks like the section isn't declared - let's declare it and try again. DeclareSection(config, sectionName); section = config.GetSection(fullSectionName) as ClientSettingsSection; } } return section; } // Declares the section handler of a given section in its section group, if a declaration isn't already // present. private void DeclareSection(Configuration config, string sectionName) { ConfigurationSectionGroup settingsGroup = config.GetSectionGroup(UserSettingsGroupName); if (settingsGroup == null) { //Declare settings group ConfigurationSectionGroup group = new UserSettingsGroup(); config.SectionGroups.Add(UserSettingsGroupName, group); } settingsGroup = config.GetSectionGroup(UserSettingsGroupName); Debug.Assert(settingsGroup != null, "Failed to declare settings group"); if (settingsGroup != null) { ConfigurationSection section = settingsGroup.Sections[sectionName]; if (section == null) { section = new ClientSettingsSection(); section.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser; section.SectionInformation.RequirePermission = false; settingsGroup.Sections.Add(sectionName, section); } } } internal IDictionary ReadSettings(string sectionName, bool isUserScoped) { IDictionary settings = new Hashtable(); if( isUserScoped && !ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) { return settings; } string prefix = isUserScoped ? UserSettingsGroupPrefix : ApplicationSettingsGroupPrefix; ConfigurationManager.RefreshSection(prefix + sectionName); ClientSettingsSection section = ConfigurationManager.GetSection(prefix + sectionName) as ClientSettingsSection; if (section != null) { foreach (SettingElement setting in section.Settings) { settings[setting.Name] = new StoredSetting(setting.SerializeAs, setting.Value.ValueXml); } } return settings; } internal static IDictionary ReadSettingsFromFile(string configFileName, string sectionName, bool isUserScoped) { IDictionary settings = new Hashtable(); if( isUserScoped && !ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) { return settings; } string prefix = isUserScoped ? UserSettingsGroupPrefix : ApplicationSettingsGroupPrefix; ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); // NOTE: When isUserScoped is true, we don't care if configFileName represents a roaming file or // a local one. All we want is three levels of configuration. So, we use the PerUserRoaming level. ConfigurationUserLevel userLevel = isUserScoped ? ConfigurationUserLevel.PerUserRoaming : ConfigurationUserLevel.None; if (isUserScoped) { fileMap.ExeConfigFilename = ConfigurationManagerInternalFactory.Instance.ApplicationConfigUri; fileMap.RoamingUserConfigFilename = configFileName; } else { fileMap.ExeConfigFilename = configFileName; } Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel); ClientSettingsSection section = config.GetSection(prefix + sectionName) as ClientSettingsSection; if (section != null) { foreach (SettingElement setting in section.Settings) { settings[setting.Name] = new StoredSetting(setting.SerializeAs, setting.Value.ValueXml); } } return settings; } internal ConnectionStringSettingsCollection ReadConnectionStrings() { return PrivilegedConfigurationManager.ConnectionStrings; } internal void RevertToParent(string sectionName, bool isRoaming) { if (!ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) { throw new ConfigurationErrorsException(SR.GetString(SR.UserSettingsNotSupported)); } Configuration config = GetUserConfig(isRoaming); ClientSettingsSection section = GetConfigSection(config, sectionName, false); // If the section is null, there is nothing to revert. if (section != null) { section.SectionInformation.RevertToParent(); config.Save(); } } internal void WriteSettings(string sectionName, bool isRoaming, IDictionary newSettings) { if (!ConfigurationManagerInternalFactory.Instance.SupportsUserConfig) { throw new ConfigurationErrorsException(SR.GetString(SR.UserSettingsNotSupported)); } Configuration config = GetUserConfig(isRoaming); ClientSettingsSection section = GetConfigSection(config, sectionName, true); if (section != null) { SettingElementCollection sec = section.Settings; foreach (DictionaryEntry entry in newSettings) { SettingElement se = sec.Get((string) entry.Key); if (se == null) { se = new SettingElement(); se.Name = (string) entry.Key; sec.Add(se); } StoredSetting ss = (StoredSetting) entry.Value; se.SerializeAs = ss.SerializeAs; se.Value.ValueXml = ss.Value; } try { config.Save(); } catch (ConfigurationErrorsException ex) { // We wrap this in an exception with our error message and throw again. throw new ConfigurationErrorsException(SR.GetString(SR.SettingsSaveFailed, ex.Message), ex); } } else { throw new ConfigurationErrorsException(SR.GetString(SR.SettingsSaveFailedNoSection)); } } /// /// A private configuration host that we use to write settings to config. We need this so we /// can enforce a quota on the size of stuff written out. /// private sealed class ClientSettingsConfigurationHost : DelegatingConfigHost { private const string ClientConfigurationHostTypeName = "System.Configuration.ClientConfigurationHost," + AssemblyRef.SystemConfiguration; private const string InternalConfigConfigurationFactoryTypeName = "System.Configuration.Internal.InternalConfigConfigurationFactory," + AssemblyRef.SystemConfiguration; private static volatile IInternalConfigConfigurationFactory s_configFactory; /// /// ClientConfigurationHost implements this - a way of getting some info from it without /// depending too much on its internals. /// private IInternalConfigClientHost ClientHost { get { return (IInternalConfigClientHost) Host; } } internal static IInternalConfigConfigurationFactory ConfigFactory { get { if (s_configFactory == null) { s_configFactory = (IInternalConfigConfigurationFactory) TypeUtil.CreateInstanceWithReflectionPermission(InternalConfigConfigurationFactoryTypeName); } return s_configFactory; } } private ClientSettingsConfigurationHost() {} public override void Init(IInternalConfigRoot configRoot, params object[] hostInitParams) { Debug.Fail("Did not expect to get called here"); } /// /// We delegate this to the ClientConfigurationHost. The only thing we need to do here is to /// build a configPath from the ConfigurationUserLevel we get passed in. /// public override void InitForConfiguration(ref string locationSubPath, out string configPath, out string locationConfigPath, IInternalConfigRoot configRoot, params object[] hostInitConfigurationParams) { ConfigurationUserLevel userLevel = (ConfigurationUserLevel) hostInitConfigurationParams[0]; string desiredConfigPath = null; Host = (IInternalConfigHost) TypeUtil.CreateInstanceWithReflectionPermission(ClientConfigurationHostTypeName); switch (userLevel) { case ConfigurationUserLevel.None: desiredConfigPath = ClientHost.GetExeConfigPath(); break; case ConfigurationUserLevel.PerUserRoaming: desiredConfigPath = ClientHost.GetRoamingUserConfigPath(); break; case ConfigurationUserLevel.PerUserRoamingAndLocal: desiredConfigPath = ClientHost.GetLocalUserConfigPath(); break; default: throw new ArgumentException(SR.GetString(SR.UnknownUserLevel)); } Host.InitForConfiguration(ref locationSubPath, out configPath, out locationConfigPath, configRoot, null, null, desiredConfigPath); } private bool IsKnownConfigFile(string filename) { return String.Equals(filename, ConfigurationManagerInternalFactory.Instance.MachineConfigPath, StringComparison.OrdinalIgnoreCase) || String.Equals(filename, ConfigurationManagerInternalFactory.Instance.ApplicationConfigUri, StringComparison.OrdinalIgnoreCase) || String.Equals(filename, ConfigurationManagerInternalFactory.Instance.ExeLocalConfigPath, StringComparison.OrdinalIgnoreCase) || String.Equals(filename, ConfigurationManagerInternalFactory.Instance.ExeRoamingConfigPath, StringComparison.OrdinalIgnoreCase); } internal static Configuration OpenExeConfiguration(ConfigurationUserLevel userLevel) { return ConfigFactory.Create(typeof(ClientSettingsConfigurationHost), userLevel); } /// /// If the stream we are asked for represents a config file that we know about, we ask /// the host to assert appropriate permissions. /// public override Stream OpenStreamForRead(string streamName) { if (IsKnownConfigFile(streamName)) { return Host.OpenStreamForRead(streamName, true); } else { return Host.OpenStreamForRead(streamName); } } /// /// If the stream we are asked for represents a user.config file that we know about, we wrap it in a /// QuotaEnforcedStream, after asking the host to assert appropriate permissions./// /// public override Stream OpenStreamForWrite(string streamName, string templateStreamName, ref object writeContext) { Stream stream = null; if (String.Equals(streamName, ConfigurationManagerInternalFactory.Instance.ExeLocalConfigPath, StringComparison.OrdinalIgnoreCase)) { stream = new QuotaEnforcedStream( Host.OpenStreamForWrite(streamName, templateStreamName, ref writeContext, true), false); } else if (String.Equals(streamName, ConfigurationManagerInternalFactory.Instance.ExeRoamingConfigPath, StringComparison.OrdinalIgnoreCase)) { stream = new QuotaEnforcedStream( Host.OpenStreamForWrite(streamName, templateStreamName, ref writeContext, true), true); } else { stream = Host.OpenStreamForWrite(streamName, templateStreamName, ref writeContext); } return stream; } /// /// If this is a stream that represents a user.config file that we know about, we ask /// the host to assert appropriate permissions. /// public override void WriteCompleted(string streamName, bool success, object writeContext) { if (String.Equals(streamName, ConfigurationManagerInternalFactory.Instance.ExeLocalConfigPath, StringComparison.OrdinalIgnoreCase) || String.Equals(streamName, ConfigurationManagerInternalFactory.Instance.ExeRoamingConfigPath, StringComparison.OrdinalIgnoreCase)) { Host.WriteCompleted(streamName, success, writeContext, true); } else { Host.WriteCompleted(streamName, success, writeContext); } } } /// /// A private stream class that wraps a stream and enforces a quota. The quota enforcement uses /// IsolatedStorageFilePermission. We override nearly all methods on the Stream class so we can /// forward to the wrapped stream. In the methods that affect stream length, we verify that the /// quota is respected before forwarding. /// private sealed class QuotaEnforcedStream : Stream { private Stream _originalStream; private bool _isRoaming; internal QuotaEnforcedStream(Stream originalStream, bool isRoaming) { _originalStream = originalStream; _isRoaming = isRoaming; Debug.Assert(_originalStream != null, "originalStream was null."); } public override bool CanRead { get { return _originalStream.CanRead; } } public override bool CanWrite { get { return _originalStream.CanWrite; } } public override bool CanSeek { get { return _originalStream.CanSeek; } } public override long Length { get { return _originalStream.Length; } } public override long Position { get { return _originalStream.Position; } set { if (value < 0) { throw new ArgumentOutOfRangeException("value", SR.GetString(SR.PositionOutOfRange)); } Seek(value, SeekOrigin.Begin); } } public override void Close() { _originalStream.Close(); } protected override void Dispose(bool disposing) { if (disposing) { if (_originalStream != null) { ((IDisposable)_originalStream).Dispose(); _originalStream = null; } } base.Dispose(disposing); } public override void Flush() { _originalStream.Flush(); } public override void SetLength(long value) { long oldLen = _originalStream.Length; long newLen = value; EnsureQuota(Math.Max(oldLen, newLen)); _originalStream.SetLength(value); } public override int Read(byte[] buffer, int offset, int count) { return _originalStream.Read(buffer, offset, count); } public override int ReadByte() { return _originalStream.ReadByte(); } public override long Seek(long offset, SeekOrigin origin) { if (!CanSeek) { throw new NotSupportedException(); } long oldLen = _originalStream.Length; long newLen; switch (origin) { case SeekOrigin.Begin: newLen = offset; break; case SeekOrigin.Current: newLen = _originalStream.Position + offset; break; case SeekOrigin.End: newLen = oldLen + offset; break; default: throw new ArgumentException(SR.GetString(SR.UnknownSeekOrigin), "origin"); } EnsureQuota(Math.Max(oldLen, newLen)); return _originalStream.Seek(offset, origin); } public override void Write(byte[] buffer, int offset, int count) { if (!CanWrite) { throw new NotSupportedException(); } long oldLen = _originalStream.Length; long newLen = _originalStream.CanSeek ? _originalStream.Position + (long)count : _originalStream.Length + (long)count; EnsureQuota(Math.Max(oldLen, newLen)); _originalStream.Write(buffer, offset, count); } public override void WriteByte(byte value) { if (!CanWrite) { throw new NotSupportedException(); } long oldLen = _originalStream.Length; long newLen = _originalStream.CanSeek ? _originalStream.Position + 1 : _originalStream.Length + 1; EnsureQuota(Math.Max(oldLen, newLen)); _originalStream.WriteByte(value); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) { return _originalStream.BeginRead(buffer, offset, numBytes, userCallback, stateObject); } public override int EndRead(IAsyncResult asyncResult) { return _originalStream.EndRead(asyncResult); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) { if (!CanWrite) { throw new NotSupportedException(); } long oldLen = _originalStream.Length; long newLen = _originalStream.CanSeek ? _originalStream.Position + (long)numBytes : _originalStream.Length + (long)numBytes; EnsureQuota(Math.Max(oldLen, newLen)); return _originalStream.BeginWrite(buffer, offset, numBytes, userCallback, stateObject); } public override void EndWrite(IAsyncResult asyncResult) { _originalStream.EndWrite(asyncResult); } // private void EnsureQuota(long size) { IsolatedStoragePermission storagePerm = new IsolatedStorageFilePermission(PermissionState.None); storagePerm.UserQuota = size; storagePerm.UsageAllowed = _isRoaming? IsolatedStorageContainment.DomainIsolationByRoamingUser : IsolatedStorageContainment.DomainIsolationByUser; storagePerm.Demand(); } } } /// /// The ClientSettingsStore talks to the LocalFileSettingsProvider through a dictionary which maps from /// setting names to StoredSetting structs. This struct contains the relevant information. /// internal struct StoredSetting { internal StoredSetting(SettingsSerializeAs serializeAs, XmlNode value) { SerializeAs = serializeAs; Value = value; } internal SettingsSerializeAs SerializeAs; internal XmlNode Value; } }