| File: Proxy\ProxyCache.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Configuration\NuGet.Configuration.csproj (NuGet.Configuration) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Net; using NuGet.Common; namespace NuGet.Configuration { public class ProxyCache : IProxyCache, IProxyCredentialCache { #if !IS_CORECLR /// <summary> /// Capture the default System Proxy so that it can be re-used by the IProxyFinder /// because we can't rely on WebRequest.DefaultWebProxy since someone can modify the DefaultWebProxy /// property and we can't tell if it was modified and if we are still using System Proxy Settings or not. /// One limitation of this method is that it does not look at the config file to get the defined proxy /// settings. /// </summary> private static readonly IWebProxy _originalSystemProxy = WebRequest.GetSystemWebProxy(); #endif private readonly ConcurrentDictionary<Uri, ICredentials> _cachedCredentials = new ConcurrentDictionary<Uri, ICredentials>(); private readonly ISettings _settings; private readonly IEnvironmentVariableReader _environment; // It's not likely that http proxy settings are set in machine wide settings, // so not passing machine wide settings to Settings.LoadDefaultSettings() should be fine. private static Lazy<ProxyCache> _instance = new Lazy<ProxyCache>(() => FromDefaultSettings()); static ProxyCache() { StaticState.StartMSBuildRestoreTasks += ResetCache; } private static ProxyCache FromDefaultSettings() { return new ProxyCache( Settings.LoadDefaultSettings(root: null, configFileName: null, machineWideSettings: null), EnvironmentVariableWrapper.Instance); } public static ProxyCache Instance => _instance.Value; /// <summary> /// Re-reads proxy settings (the <c>http_proxy</c> family of environment variables and nuget.config) by /// discarding the cached instance, so a process reused across builds observes the current proxy /// configuration and does not retain previously cached proxy credentials. /// </summary> internal static void ResetCache() => _instance = new Lazy<ProxyCache>(() => FromDefaultSettings()); public Guid Version { get; private set; } = Guid.NewGuid(); public ProxyCache(ISettings settings, IEnvironmentVariableReader environment) { _settings = settings; _environment = environment; } public IWebProxy? GetProxy(Uri sourceUri) { // Check if the user has configured proxy details in settings or in the environment. var configuredProxy = GetUserConfiguredProxy(); if (configuredProxy != null) { TryAddProxyCredentialsToCache(configuredProxy); configuredProxy.Credentials = this; return configuredProxy; } #if !IS_CORECLR if (IsSystemProxySet(sourceUri)) { var systemProxy = GetSystemProxy(sourceUri); TryAddProxyCredentialsToCache(systemProxy); systemProxy.Credentials = this; return systemProxy; } #endif return null; } // Adds new proxy credentials to cache if there's not any in there yet private bool TryAddProxyCredentialsToCache(WebProxy configuredProxy) { // If a proxy was cached, it means the stored credentials are incorrect. Use the cached one in this case. var proxyCredentials = configuredProxy.Credentials ?? CredentialCache.DefaultCredentials; return _cachedCredentials.TryAdd(configuredProxy.ProxyAddress, proxyCredentials); } public WebProxy? GetUserConfiguredProxy() { // Try reading from the settings. The values are stored as 3 config values http_proxy, http_proxy.user, http_proxy.password var host = SettingsUtility.GetConfigValue(_settings, ConfigurationConstants.HostKey); if (!string.IsNullOrEmpty(host)) { // The host is the minimal value we need to assume a user configured proxy. var webProxy = new WebProxy(host!); if (RuntimeEnvironmentHelper.IsWindows) { var userName = SettingsUtility.GetConfigValue(_settings, ConfigurationConstants.UserKey); var password = SettingsUtility.GetDecryptedValueForAddItem(_settings, ConfigurationConstants.Config, ConfigurationConstants.PasswordKey); if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) { webProxy.Credentials = new NetworkCredential(userName, password); } } var noProxy = SettingsUtility.GetConfigValue(_settings, ConfigurationConstants.NoProxy); if (!string.IsNullOrEmpty(noProxy)) { // split comma-separated list of domains webProxy.BypassList = noProxy!.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } return webProxy; } // Next try reading from the environment variable http_proxy. This would be specified as http://<username>:<password>@proxy.com host = _environment.GetEnvironmentVariable(ConfigurationConstants.HostKey); Uri? uri; if (!string.IsNullOrEmpty(host) && Uri.TryCreate(host, UriKind.Absolute, out uri)) { var webProxy = new WebProxy(uri.GetComponents( UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped)); if (!string.IsNullOrEmpty(uri.UserInfo)) { var credentials = uri.UserInfo.Split(':'); if (credentials.Length > 1) { webProxy.Credentials = new NetworkCredential( userName: credentials[0], password: credentials[1]); } } var noProxy = _environment.GetEnvironmentVariable(ConfigurationConstants.NoProxy); if (!string.IsNullOrEmpty(noProxy)) { // split comma-separated list of domains webProxy.BypassList = noProxy!.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } return webProxy; } return null; } public void UpdateCredential(Uri proxyAddress, NetworkCredential credentials) { if (credentials == null) { throw new ArgumentNullException(nameof(credentials)); } _cachedCredentials.AddOrUpdate( proxyAddress, addValueFactory: _ => { Version = Guid.NewGuid(); return credentials; }, updateValueFactory: (_, __) => { Version = Guid.NewGuid(); return credentials; }); } public NetworkCredential? GetCredential(Uri proxyAddress, string authType) { ICredentials? cachedCredentials; if (_cachedCredentials.TryGetValue(proxyAddress, out cachedCredentials)) { return cachedCredentials.GetCredential(proxyAddress, authType); } return null; } #if !IS_CORECLR private static WebProxy GetSystemProxy(Uri uri) { // WebRequest.DefaultWebProxy seems to be more capable in terms of getting the default // proxy settings instead of the WebRequest.GetSystemProxy() var proxyUri = _originalSystemProxy.GetProxy(uri); return new WebProxy(proxyUri); } /// <summary> /// Return true or false if connecting through a proxy server /// </summary> /// <param name="uri"></param> /// <returns></returns> private static bool IsSystemProxySet(Uri uri) { // The reason for not calling the GetSystemProxy is because the object // that will be returned is no longer going to be the proxy that is set by the settings // on the users machine only the Address is going to be the same. // Not sure why the .NET team did not want to expose all of the useful settings like // ByPass list and other settings that we can't get because of it. // Anyway the reason why we need the DefaultWebProxy is to see if the uri that we are // getting the proxy for to should be bypassed or not. If it should be bypassed then // return that we don't need a proxy and we should try to connect directly. var proxy = WebRequest.DefaultWebProxy; if (proxy != null) { var proxyUri = proxy.GetProxy(uri); if (proxyUri != null) { var proxyAddress = new Uri(proxyUri.AbsoluteUri); if (string.Equals(proxyAddress.AbsoluteUri, uri.AbsoluteUri, PathUtility.GetStringComparisonBasedOnOS())) { return false; } return !proxy.IsBypassed(uri); } } return false; } #endif } }