File: System\Configuration\ClientConfigurationHost.cs
Project: ndp\fx\src\Configuration\System.Configuration.csproj (System.Configuration)
//------------------------------------------------------------------------------
// <copyright file="ClientConfigurationHost.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Configuration {
    using System.Configuration.Internal;
    using System.IO;
    using System.Security.Policy;
    using System.Security.Permissions;
    using System.Reflection;
    using System.Threading;
    using System.Security;
    using System.Net;
    using System.Security.Principal;
    using System.Diagnostics.CodeAnalysis;
 
    internal sealed class ClientConfigurationHost : DelegatingConfigHost, IInternalConfigClientHost {
        internal const string MachineConfigName = "MACHINE";
        internal const string ExeConfigName = "EXE";
        internal const string RoamingUserConfigName = "ROAMING_USER";
        internal const string LocalUserConfigName = "LOCAL_USER";
 
        internal const string MachineConfigPath = MachineConfigName;
        internal const string ExeConfigPath = MachineConfigPath + "/" + ExeConfigName;
        internal const string RoamingUserConfigPath = ExeConfigPath + "/" + RoamingUserConfigName;
        internal const string LocalUserConfigPath = RoamingUserConfigPath + "/" + LocalUserConfigName;
 
        private const string ConfigExtension = ".config";
        private const string MachineConfigFilename = "machine.config";
        private const string MachineConfigSubdirectory = "Config";
 
        private static object                   s_init = new object();
        private static object                   s_version = new object();
        private static volatile string          s_machineConfigFilePath;
 
        private string                          _exePath;       // the physical path to the exe being configured
        private ClientConfigPaths               _configPaths;   // physical paths to client config files
        private ExeConfigurationFileMap         _fileMap;       // optional file map
        private bool                            _initComplete;
 
        internal ClientConfigurationHost() {
            Host = new InternalConfigHost();
        }
 
        internal ClientConfigPaths ConfigPaths {
            get {
                if (_configPaths == null) {
                    _configPaths = ClientConfigPaths.GetPaths(_exePath, _initComplete);
                }
 
                return _configPaths;
            }
        }
 
        internal void RefreshConfigPaths() {
            // Refresh current config paths.
            if (_configPaths != null && !_configPaths.HasEntryAssembly && _exePath == null) {
                ClientConfigPaths.RefreshCurrent();
                _configPaths = null;
            }
        }
 
        static internal string MachineConfigFilePath {
            [FileIOPermissionAttribute(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.PathDiscovery)]
            [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "The callers do not expose this information without performing the appropriate demands themselves.")]
            get {
                if (s_machineConfigFilePath == null) {
                    string directory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
                    s_machineConfigFilePath = Path.Combine(Path.Combine(directory, MachineConfigSubdirectory), MachineConfigFilename);
                }
 
                return s_machineConfigFilePath;
            }
        }
 
        internal bool HasRoamingConfig {
            get {
                if (_fileMap != null) {
                    return !String.IsNullOrEmpty(_fileMap.RoamingUserConfigFilename);
                }
                else {
                    return ConfigPaths.HasRoamingConfig;
                }
            }
        }
 
        internal bool HasLocalConfig {
            get {
                if (_fileMap != null) {
                    return !String.IsNullOrEmpty(_fileMap.LocalUserConfigFilename);
                }
                else {
                    return ConfigPaths.HasLocalConfig;
                }
            }
        }
 
        internal bool IsAppConfigHttp {
            get {
                return !IsFile(GetStreamName(ExeConfigPath));
            }
        }
 
        // IInternalConfigClientHost methods are used by Venus and Whitehorse 
        // so as not to require explicit knowledge of the contents of the
        // config path.
 
        // return true if the config path is for an exe config, false otherwise.
        bool IInternalConfigClientHost.IsExeConfig(string configPath) {
            return StringUtil.EqualsIgnoreCase(configPath, ExeConfigPath);
        }
 
        bool IInternalConfigClientHost.IsRoamingUserConfig(string configPath) {
            return StringUtil.EqualsIgnoreCase(configPath, RoamingUserConfigPath);
        }
 
        bool IInternalConfigClientHost.IsLocalUserConfig(string configPath) {
            return StringUtil.EqualsIgnoreCase(configPath, LocalUserConfigPath);
        }
 
        // Return true if the config path is for a user.config file, false otherwise.
        private bool IsUserConfig(string configPath) {
            return StringUtil.EqualsIgnoreCase(configPath, RoamingUserConfigPath) ||
                   StringUtil.EqualsIgnoreCase(configPath, LocalUserConfigPath);
        }
 
        string IInternalConfigClientHost.GetExeConfigPath() {
            return ExeConfigPath;
        }
 
        string IInternalConfigClientHost.GetRoamingUserConfigPath() {
            return RoamingUserConfigPath;
        }
 
        string IInternalConfigClientHost.GetLocalUserConfigPath() {
            return LocalUserConfigPath;
        }
 
        public override void Init(IInternalConfigRoot configRoot, params object[] hostInitParams) {
            try {
                ConfigurationFileMap fileMap = (ConfigurationFileMap)   hostInitParams[0];
                _exePath = (string)                                     hostInitParams[1];
 
                Host.Init(configRoot, hostInitParams);
 
                // Do not complete initialization in runtime config, to avoid expense of 
                // loading user.config files that may not be required.
                _initComplete = configRoot.IsDesignTime;
 
                if (fileMap != null && !String.IsNullOrEmpty(_exePath)) {
                    throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::Init");
                }
 
                if (String.IsNullOrEmpty(_exePath)) {
                    _exePath = null;
                }
 
                // Initialize the fileMap, if provided.
                if (fileMap != null) {
                    _fileMap = new ExeConfigurationFileMap();
                    if (!String.IsNullOrEmpty(fileMap.MachineConfigFilename)) {
                        _fileMap.MachineConfigFilename = Path.GetFullPath(fileMap.MachineConfigFilename);
                    }
 
                    ExeConfigurationFileMap exeFileMap = fileMap as ExeConfigurationFileMap;
                    if (exeFileMap != null) {
                        if (!String.IsNullOrEmpty(exeFileMap.ExeConfigFilename)) {
                            _fileMap.ExeConfigFilename = Path.GetFullPath(exeFileMap.ExeConfigFilename);
                        }
 
                        if (!String.IsNullOrEmpty(exeFileMap.RoamingUserConfigFilename)) {
                            _fileMap.RoamingUserConfigFilename = Path.GetFullPath(exeFileMap.RoamingUserConfigFilename);
                        }
 
                        if (!String.IsNullOrEmpty(exeFileMap.LocalUserConfigFilename)) {
                            _fileMap.LocalUserConfigFilename = Path.GetFullPath(exeFileMap.LocalUserConfigFilename); 
                        }
                    }
                }
            }
            catch (SecurityException) {
                // Lets try to give them some information telling them 
                // they don't have enough security privileges
                throw new ConfigurationErrorsException(
                    SR.GetString(SR.Config_client_config_init_security));
            }
            catch {
                throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::Init");
            }
        }
 
        public override void InitForConfiguration(ref string locationSubPath, out string configPath, out string locationConfigPath, 
                IInternalConfigRoot configRoot, params object[] hostInitConfigurationParams) {
            
            locationSubPath = null;
            configPath = (string) hostInitConfigurationParams[2];
            locationConfigPath = null;
 
            Init(configRoot, hostInitConfigurationParams);
        }
 
        // Delay init if we have not been asked to complete init, and it is a user.config file.
        public override bool IsInitDelayed(IInternalConfigRecord configRecord) {
            return !_initComplete && IsUserConfig(configRecord.ConfigPath);
        }
 
        public override void RequireCompleteInit(IInternalConfigRecord record) {
            // Loading information about user.config files is expensive, 
            // so do it just once by locking.
            lock (this) {
                if (!_initComplete) {
                    // Note that all future requests for config must be complete.
                    _initComplete = true;
 
                    // Throw out the ConfigPath for this exe.
                    ClientConfigPaths.RefreshCurrent();
 
                    // Throw out our cached copy.
                    _configPaths = null;
 
                    // Force loading of user.config file information under lock.
                    ClientConfigPaths configPaths = ConfigPaths;
                }
            }
        }
 
        // config path support
        public override bool IsConfigRecordRequired(string configPath) {
            string configName = ConfigPathUtility.GetName(configPath);
            switch (configName) {
                default:
                    // should never get here
                    return false;
 
                case MachineConfigName:
                case ExeConfigName:
                    return true;
 
                case RoamingUserConfigName:
                    // Makes the design easier even if we only have an empty Roaming config record.
                    return HasRoamingConfig || HasLocalConfig;
 
                case LocalUserConfigName:
                    return HasLocalConfig;
            }
        }
 
        // stream support
        public override string GetStreamName(string configPath) {
            string configName = ConfigPathUtility.GetName(configPath);
            if (_fileMap != null) {
                switch (configName) {
                    default:
                        // should never get here
                        goto case MachineConfigName;
 
                    case MachineConfigName:
                        return _fileMap.MachineConfigFilename;
 
                    case ExeConfigName:
                        return _fileMap.ExeConfigFilename;
 
                    case RoamingUserConfigName:
                        return _fileMap.RoamingUserConfigFilename;
 
                    case LocalUserConfigName:
                        return _fileMap.LocalUserConfigFilename;
                }
            }
            else {
                switch (configName) {
                    default:
                        // should never get here
                        goto case MachineConfigName;
 
                    case MachineConfigName:
                        return MachineConfigFilePath;
 
                    case ExeConfigName:
                        return ConfigPaths.ApplicationConfigUri;
 
                    case RoamingUserConfigName:
                        return ConfigPaths.RoamingConfigFilename;
 
                    case LocalUserConfigName:
                        return ConfigPaths.LocalConfigFilename;
                }
            }
        }
 
        public override string GetStreamNameForConfigSource(string streamName, string configSource) {
            if (IsFile(streamName)) {
                return Host.GetStreamNameForConfigSource(streamName, configSource);
            }
 
            int index = streamName.LastIndexOf('/');
            if (index < 0)
                return null;
 
            string parentUri = streamName.Substring(0, index + 1);
            string result = parentUri + configSource.Replace('\\', '/');
 
            return result;
        }
 
        public override object GetStreamVersion(string streamName) {
            if (IsFile(streamName)) {
                return Host.GetStreamVersion(streamName);
            }
 
            // assume it is the same
            return s_version;
        }
 
 
        // default impl treats name as a file name
        // null means stream doesn't exist for this name
        public override Stream OpenStreamForRead(string streamName) {
            // the streamName can either be a file name, or a URI
            if (IsFile(streamName)) {
                return Host.OpenStreamForRead(streamName);
            }
 
            if (streamName == null) {
                return null;
            }
            
            // scheme is http
            WebClient client = new WebClient();
 
            // Try using default credentials
            try {
                client.Credentials = CredentialCache.DefaultCredentials;
            }
            catch {
            }
 
            byte[] fileData = null;
            try {
                fileData = client.DownloadData(streamName);
            }
            catch {
            }
 
            if (fileData == null) {
                return null;
            }
 
            MemoryStream stream = new MemoryStream(fileData);
            return stream;
        }
 
        public override Stream OpenStreamForWrite(string streamName, string templateStreamName, ref object writeContext) {
            // only support files, not URIs
            if (!IsFile(streamName)) {
                throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::OpenStreamForWrite");
            }
 
            return Host.OpenStreamForWrite(streamName, templateStreamName, ref writeContext);
        }
 
        public override void DeleteStream(string streamName) {
            // only support files, not URIs
            if (!IsFile(streamName)) {
                throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::Delete");
            }
 
            Host.DeleteStream(streamName);
        }
 
        // RefreshConfig support - runtime only
        public override bool SupportsRefresh {
            get {return true;}
        }
 
        // path support
        public override bool SupportsPath {
            get {return false;}
        }
 
        // Do we support location tags?
        public override bool SupportsLocation {
            get {return false;}
        }
 
        public override bool IsDefinitionAllowed(string configPath, ConfigurationAllowDefinition allowDefinition, ConfigurationAllowExeDefinition allowExeDefinition) {
            string allowedConfigPath;
 
            switch (allowExeDefinition) {
                case ConfigurationAllowExeDefinition.MachineOnly:
                    allowedConfigPath = MachineConfigPath;
                    break;
 
                case ConfigurationAllowExeDefinition.MachineToApplication:
                    allowedConfigPath = ExeConfigPath;
                    break;
 
                case ConfigurationAllowExeDefinition.MachineToRoamingUser:
                    allowedConfigPath = RoamingUserConfigPath;
                    break;
 
                // MachineToLocalUser does not current have any definition restrictions
                case ConfigurationAllowExeDefinition.MachineToLocalUser:
                    return true;
 
                default:
                    // If we have extended ConfigurationAllowExeDefinition
                    // make sure to update this switch accordingly
                    throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::IsDefinitionAllowed");
            }
 
            return configPath.Length <= allowedConfigPath.Length;
        }
 
        public override void VerifyDefinitionAllowed(string configPath, ConfigurationAllowDefinition allowDefinition, ConfigurationAllowExeDefinition allowExeDefinition, IConfigErrorInfo errorInfo) {
            if (!IsDefinitionAllowed(configPath, allowDefinition, allowExeDefinition)) {
                switch (allowExeDefinition) {
                    case ConfigurationAllowExeDefinition.MachineOnly:
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.Config_allow_exedefinition_error_machine), errorInfo);
 
                    case ConfigurationAllowExeDefinition.MachineToApplication:
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.Config_allow_exedefinition_error_application), errorInfo);
 
                    case ConfigurationAllowExeDefinition.MachineToRoamingUser:
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.Config_allow_exedefinition_error_roaminguser), errorInfo);
 
                    default:
                        // If we have extended ConfigurationAllowExeDefinition
                        // make sure to update this switch accordingly
                        throw ExceptionUtil.UnexpectedError("ClientConfigurationHost::VerifyDefinitionAllowed");
                }
            }
        }
 
        // prefetch support
        public override bool PrefetchAll(string configPath, string streamName) {
            // If it's a file, we don't need to.  Otherwise (e.g. it's from the web), we'll prefetch everything.
            return !IsFile(streamName);
        }
 
        public override bool PrefetchSection(string sectionGroupName, string sectionName) {
            return sectionGroupName == "system.net";
        }
 
        // we trust machine.config - admins settings do not have security restrictions.
        public override bool IsTrustedConfigPath(string configPath) {
            return configPath == MachineConfigPath;
        }
 
        [SecurityPermission(SecurityAction.Assert, ControlEvidence=true)]
        public override void GetRestrictedPermissions(IInternalConfigRecord configRecord, out PermissionSet permissionSet, out bool isHostReady) {
            // Get the stream name as a URL
            string url;
            bool isFile = IsFile(configRecord.StreamName);
            if (isFile) {
                url = UrlPath.ConvertFileNameToUrl(configRecord.StreamName);
            }
            else {
                url = configRecord.StreamName;
            }
 
            Evidence evidence = new Evidence();
            
            // Add Url evidence, which is simply the URL.
            evidence.AddHostEvidence(new Url(url));
 
            // Add Zone evidence - My Computer, Intranet, Internet, etc.
            evidence.AddHostEvidence(Zone.CreateFromUrl(url));
 
            // Add Site evidence if the url is http.
            if (!isFile) {
                evidence.AddHostEvidence(Site.CreateFromUrl(url));
            }
 
            // Get the resulting permission set.
            permissionSet = SecurityManager.GetStandardSandbox(evidence);
 
            // Client host is always ready to return permissions.
            isHostReady = true;
        }
 
        //
	// Impersonate for Client Config
        // Use the process identity
        //
        [SecurityPermissionAttribute(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal | SecurityPermissionFlag.UnmanagedCode)]
        public override IDisposable Impersonate() {
            // Use the process identity
            return WindowsIdentity.Impersonate(IntPtr.Zero);
        }
 
	// context support
        public override object CreateDeprecatedConfigContext(string configPath) {
            return null;
        }
 
        // CreateConfigurationContext
        //
        // Create the new context
        //
        public override object 
        CreateConfigurationContext( string configPath,
                                    string locationSubPath )
        {
            return new ExeContext(GetUserLevel(configPath), ConfigPaths.ApplicationUri);
        }
        
        // GetUserLevel
        //
        // Given a configPath, determine what the user level is?
        //
        private ConfigurationUserLevel GetUserLevel(string configPath)
        {
            ConfigurationUserLevel level;
            
            switch (ConfigPathUtility.GetName(configPath)) {
                case MachineConfigName:
                    // Machine Level
                    level = ConfigurationUserLevel.None;
                    break;
 
                case ExeConfigName:
                    // Exe Level
                    level = ConfigurationUserLevel.None;
                    break;
 
                case LocalUserConfigName:
                    // User Level
                    level = ConfigurationUserLevel.PerUserRoamingAndLocal;
                    break;
                    
                case RoamingUserConfigName:
                    // Roaming Level
                    level = ConfigurationUserLevel.PerUserRoaming;
                    break;
 
                default:
                    Debug.Fail("unrecognized configPath " + configPath);
                    level = ConfigurationUserLevel.None;
                    break;
            }
 
            return level;
        }
 
        //
        // Create a Configuration object.
        //
        static internal Configuration OpenExeConfiguration(ConfigurationFileMap fileMap, bool isMachine, ConfigurationUserLevel userLevel, string exePath) {
            // validate userLevel argument
            switch (userLevel) {
                default:
                    throw ExceptionUtil.ParameterInvalid("userLevel");
 
                case ConfigurationUserLevel.None:
                case ConfigurationUserLevel.PerUserRoaming:
                case ConfigurationUserLevel.PerUserRoamingAndLocal:
                    break;
            }
 
            // validate fileMap arguments
            if (fileMap != null) {
                if (String.IsNullOrEmpty(fileMap.MachineConfigFilename)) {
                    throw ExceptionUtil.ParameterNullOrEmpty("fileMap.MachineConfigFilename");
                }
 
                ExeConfigurationFileMap exeFileMap = fileMap as ExeConfigurationFileMap;
                if (exeFileMap != null) {
                    switch (userLevel) {
                        case ConfigurationUserLevel.None:
                            if (String.IsNullOrEmpty(exeFileMap.ExeConfigFilename)) {
                                throw ExceptionUtil.ParameterNullOrEmpty("fileMap.ExeConfigFilename");
                            }
 
                            break;
 
                        case ConfigurationUserLevel.PerUserRoaming:
                            if (String.IsNullOrEmpty(exeFileMap.RoamingUserConfigFilename)) {
                                throw ExceptionUtil.ParameterNullOrEmpty("fileMap.RoamingUserConfigFilename");
                            }
 
                            goto case ConfigurationUserLevel.None;
 
                        case ConfigurationUserLevel.PerUserRoamingAndLocal:
                            if (String.IsNullOrEmpty(exeFileMap.LocalUserConfigFilename)) {
                                throw ExceptionUtil.ParameterNullOrEmpty("fileMap.LocalUserConfigFilename");
                            }
 
                            goto case ConfigurationUserLevel.PerUserRoaming;
                    }
                }
            }
 
            string configPath = null;
            if (isMachine) {
                configPath = MachineConfigPath;
            }
            else {
                switch (userLevel) {
                    case ConfigurationUserLevel.None:
                        configPath = ExeConfigPath;
                        break;
 
                    case ConfigurationUserLevel.PerUserRoaming:
                        configPath = RoamingUserConfigPath;
                        break;
 
                    case ConfigurationUserLevel.PerUserRoamingAndLocal:
                        configPath = LocalUserConfigPath;
                        break;
                }
            }
 
            Configuration configuration = new Configuration(null, typeof(ClientConfigurationHost), fileMap, exePath, configPath);
 
            return configuration;
        }
    }
}