File: System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs
Web Access
Project: src\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
 
namespace System.Net.Http
{
    internal sealed class HttpEnvironmentProxyCredentials : ICredentials
    {
        // Wrapper class for cases when http and https has different authentication.
        private readonly NetworkCredential? _httpCred;
        private readonly NetworkCredential? _httpsCred;
        private readonly Uri? _httpProxy;
        private readonly Uri? _httpsProxy;
 
        public HttpEnvironmentProxyCredentials(Uri? httpProxy, NetworkCredential? httpCred,
                                                Uri? httpsProxy, NetworkCredential? httpsCred)
        {
            _httpCred = httpCred;
            _httpsCred = httpsCred;
            _httpProxy = httpProxy;
            _httpsProxy = httpsProxy;
        }
 
        public NetworkCredential? GetCredential(Uri? uri, string authType)
        {
            if (uri == null)
            {
                return null;
            }
            return uri.Equals(_httpProxy) ? _httpCred :
                   uri.Equals(_httpsProxy) ? _httpsCred : null;
        }
 
        public static HttpEnvironmentProxyCredentials? TryCreate(Uri? httpProxy, Uri? httpsProxy)
        {
            NetworkCredential? httpCred = null;
            NetworkCredential? httpsCred = null;
 
            if (httpProxy != null)
            {
                httpCred = GetCredentialsFromString(httpProxy.UserInfo);
            }
            if (httpsProxy != null)
            {
                httpsCred = GetCredentialsFromString(httpsProxy.UserInfo);
            }
            if (httpCred == null && httpsCred == null)
            {
                return null;
            }
            return new HttpEnvironmentProxyCredentials(httpProxy, httpCred, httpsProxy, httpsCred);
        }
 
        /// <summary>
        /// Converts string containing user:password to NetworkCredential object
        /// </summary>
        private static NetworkCredential? GetCredentialsFromString(string? value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }
 
            if (value == ":")
            {
                return CredentialCache.DefaultNetworkCredentials;
            }
 
            value = Uri.UnescapeDataString(value);
 
            string password = "";
            string? domain = null;
            int idx = value.IndexOf(':');
            if (idx != -1)
            {
                password = value.Substring(idx + 1);
                value = value.Substring(0, idx);
            }
 
            idx = value.IndexOf('\\');
            if (idx != -1)
            {
                domain = value.Substring(0, idx);
                value = value.Substring(idx + 1);
            }
 
            return new NetworkCredential(value, password, domain);
        }
    }
 
    internal sealed partial class HttpEnvironmentProxy : IWebProxy
    {
        private const string EnvAllProxyUC = "ALL_PROXY";
        private const string EnvHttpProxyUC = "HTTP_PROXY";
        private const string EnvHttpsProxyUC = "HTTPS_PROXY";
        private const string EnvNoProxyUC = "NO_PROXY";
        private const string EnvCGI = "GATEWAY_INTERFACE"; // Running in a CGI environment.
 
        private readonly Uri? _httpProxyUri;       // String URI for HTTP requests
        private readonly Uri? _httpsProxyUri;      // String URI for HTTPS requests
        private readonly string[]? _bypass;        // list of domains not to proxy
        private ICredentials? _credentials;
 
        private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList)
        {
            _httpProxyUri = httpProxy;
            _httpsProxyUri = httpsProxy;
 
            _credentials = HttpEnvironmentProxyCredentials.TryCreate(httpProxy, httpsProxy);
            _bypass = bypassList?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
        }
 
        /// <summary>
        /// This function will evaluate given string and it will try to convert
        /// it to Uri object. The string could contain URI fragment, IP address and  port
        /// tuple or just IP address or name. It will return null if parsing fails.
        /// </summary>
        private static Uri? GetUriFromString(string? value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return null;
            }
 
            int hostIndex = 0;
            string protocol = "http";
            ushort port = 80;
 
            if (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
            {
                hostIndex = 7;
            }
            else if (value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                hostIndex = 8;
                protocol = "https";
                port = 443;
            }
            else if (value.StartsWith("socks4://", StringComparison.OrdinalIgnoreCase))
            {
                hostIndex = 9;
                protocol = "socks4";
            }
            else if (value.StartsWith("socks5://", StringComparison.OrdinalIgnoreCase))
            {
                hostIndex = 9;
                protocol = "socks5";
            }
            else if (value.StartsWith("socks4a://", StringComparison.OrdinalIgnoreCase))
            {
                hostIndex = 10;
                protocol = "socks4a";
            }
 
            if (hostIndex > 0)
            {
                value = value.Substring(hostIndex);
            }
 
            string? user = null;
            string? password = null;
            string host;
 
            // Check if there is authentication part with user and possibly password.
            // Curl accepts raw text and that may break URI parser.
            int separatorIndex = value.LastIndexOf('@');
            if (separatorIndex != -1)
            {
                // The User and password may or may not be URL encoded.
                // Curl seems to accept both. To match that, we also decode the value.
                string auth = Uri.UnescapeDataString(value.AsSpan(0, separatorIndex));
 
                value = value.Substring(separatorIndex + 1);
                separatorIndex = auth.IndexOf(':');
                if (separatorIndex == -1)
                {
                    user = auth;
                }
                else
                {
                    user = auth.Substring(0, separatorIndex);
                    password = auth.Substring(separatorIndex + 1);
                }
            }
 
            int ipV6AddressEnd = value.IndexOf(']');
            separatorIndex = value.LastIndexOf(':');
            // No ':' or it is part of IPv6 address.
            if (separatorIndex == -1 || (ipV6AddressEnd != -1 && separatorIndex < ipV6AddressEnd))
            {
                host = value;
            }
            else
            {
                host = value.Substring(0, separatorIndex);
                int endIndex = separatorIndex + 1;
                // Strip any trailing characters after port number.
                while (endIndex < value.Length)
                {
                    if (!char.IsDigit(value[endIndex]))
                    {
                        break;
                    }
                    endIndex += 1;
                }
 
                if (!ushort.TryParse(value.AsSpan(separatorIndex + 1, endIndex - separatorIndex - 1), out port))
                {
                    return null;
                }
            }
 
            try
            {
                UriBuilder ub = new UriBuilder(protocol, host, port);
                if (user != null)
                {
                    ub.UserName = Uri.EscapeDataString(user);
                }
 
                if (password != null)
                {
                    ub.Password = Uri.EscapeDataString(password);
                }
 
                Uri uri = ub.Uri;
 
                // if both user and password exist and are empty we should preserve that and use default credentials.
                // UriBuilder does not handle that now e.g. does not distinguish between empty and missing.
                if (user == "" && password == "")
                {
                    Span<Range> tokens = stackalloc Range[3];
                    ReadOnlySpan<char> uriSpan = uri.ToString();
                    if (uriSpan.Split(tokens, '/') == 3)
                    {
                        uri = new Uri($"{uriSpan[tokens[0]]}//:@{uriSpan[tokens[2]]}");
                    }
                }
 
                return uri;
            }
            catch { };
            return null;
        }
 
        /// <summary>
        /// This function returns true if given Host match bypass list.
        /// Note, that the list is common for http and https.
        /// </summary>
        private bool IsMatchInBypassList(Uri input)
        {
            if (_bypass != null)
            {
                foreach (string s in _bypass)
                {
                    if (s[0] == '.')
                    {
                        // This should match either domain it self or any subdomain or host
                        // .foo.com will match foo.com it self or *.foo.com
                        if ((s.Length - 1) == input.Host.Length &&
                            string.Compare(s, 1, input.Host, 0, input.Host.Length, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            return true;
                        }
                        else if (input.Host.EndsWith(s, StringComparison.OrdinalIgnoreCase))
                        {
                            return true;
                        }
 
                    }
                    else
                    {
                        if (string.Equals(s, input.Host, StringComparison.OrdinalIgnoreCase))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
 
        /// <summary>
        /// Gets the proxy URI. (iWebProxy interface)
        /// </summary>
        public Uri? GetProxy(Uri uri)
        {
            return HttpUtilities.IsSupportedNonSecureScheme(uri.Scheme) ? _httpProxyUri : _httpsProxyUri;
        }
 
        /// <summary>
        /// Checks if URI is subject to proxy or not.
        /// </summary>
        public bool IsBypassed(Uri uri)
        {
            return GetProxy(uri) == null ? true : IsMatchInBypassList(uri);
        }
 
        public ICredentials? Credentials
        {
            get
            {
                return _credentials;
            }
            set
            {
                _credentials = value;
            }
        }
    }
}