< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 55
Coverable lines: 55
Total lines: 345
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
GetCredential(...)0%660%
TryCreate(...)0%880%
GetCredentialsFromString(...)0%880%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5
 6namespace System.Net.Http
 7{
 8    internal sealed class HttpEnvironmentProxyCredentials : ICredentials
 9    {
 10        // Wrapper class for cases when http and https has different authentication.
 11        private readonly NetworkCredential? _httpCred;
 12        private readonly NetworkCredential? _httpsCred;
 13        private readonly Uri? _httpProxy;
 14        private readonly Uri? _httpsProxy;
 15
 016        public HttpEnvironmentProxyCredentials(Uri? httpProxy, NetworkCredential? httpCred,
 017                                                Uri? httpsProxy, NetworkCredential? httpsCred)
 018        {
 019            _httpCred = httpCred;
 020            _httpsCred = httpsCred;
 021            _httpProxy = httpProxy;
 022            _httpsProxy = httpsProxy;
 023        }
 24
 25        public NetworkCredential? GetCredential(Uri? uri, string authType)
 026        {
 027            if (uri == null)
 028            {
 029                return null;
 30            }
 031            return uri.Equals(_httpProxy) ? _httpCred :
 032                   uri.Equals(_httpsProxy) ? _httpsCred : null;
 033        }
 34
 35        public static HttpEnvironmentProxyCredentials? TryCreate(Uri? httpProxy, Uri? httpsProxy)
 036        {
 037            NetworkCredential? httpCred = null;
 038            NetworkCredential? httpsCred = null;
 39
 040            if (httpProxy != null)
 041            {
 042                httpCred = GetCredentialsFromString(httpProxy.UserInfo);
 043            }
 044            if (httpsProxy != null)
 045            {
 046                httpsCred = GetCredentialsFromString(httpsProxy.UserInfo);
 047            }
 048            if (httpCred == null && httpsCred == null)
 049            {
 050                return null;
 51            }
 052            return new HttpEnvironmentProxyCredentials(httpProxy, httpCred, httpsProxy, httpsCred);
 053        }
 54
 55        /// <summary>
 56        /// Converts string containing user:password to NetworkCredential object
 57        /// </summary>
 58        private static NetworkCredential? GetCredentialsFromString(string? value)
 059        {
 060            if (string.IsNullOrWhiteSpace(value))
 061            {
 062                return null;
 63            }
 64
 065            if (value == ":")
 066            {
 067                return CredentialCache.DefaultNetworkCredentials;
 68            }
 69
 070            value = Uri.UnescapeDataString(value);
 71
 072            string password = "";
 073            string? domain = null;
 074            int idx = value.IndexOf(':');
 075            if (idx != -1)
 076            {
 077                password = value.Substring(idx + 1);
 078                value = value.Substring(0, idx);
 079            }
 80
 081            idx = value.IndexOf('\\');
 082            if (idx != -1)
 083            {
 084                domain = value.Substring(0, idx);
 085                value = value.Substring(idx + 1);
 086            }
 87
 088            return new NetworkCredential(value, password, domain);
 089        }
 90    }
 91
 92    internal sealed partial class HttpEnvironmentProxy : IWebProxy
 93    {
 94        private const string EnvAllProxyUC = "ALL_PROXY";
 95        private const string EnvHttpProxyUC = "HTTP_PROXY";
 96        private const string EnvHttpsProxyUC = "HTTPS_PROXY";
 97        private const string EnvNoProxyUC = "NO_PROXY";
 98        private const string EnvCGI = "GATEWAY_INTERFACE"; // Running in a CGI environment.
 99
 100        private readonly Uri? _httpProxyUri;                  // String URI for HTTP requests
 101        private readonly Uri? _httpsProxyUri;                 // String URI for HTTPS requests
 102        private readonly List<string>? _bypass;               // list of domains or IP addresses not to proxy
 103        private readonly List<IPNetwork>? _bypassNetworks;    // list of subnet ranges not to proxy
 104        private ICredentials? _credentials;
 105
 106        private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList)
 107        {
 108            _httpProxyUri = httpProxy;
 109            _httpsProxyUri = httpsProxy;
 110
 111            _credentials = HttpEnvironmentProxyCredentials.TryCreate(httpProxy, httpsProxy);
 112
 113            if (bypassList != null)
 114            {
 115                foreach (string entry in bypassList.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOption
 116                {
 117                    if (IPNetwork.TryParse(entry, out IPNetwork networkEntry))
 118                    {
 119                        _bypassNetworks ??= new List<IPNetwork>();
 120                        _bypassNetworks.Add(networkEntry);
 121                    }
 122                    else
 123                    {
 124                        _bypass ??= new List<string>();
 125                        _bypass.Add(entry);
 126                    }
 127                }
 128            }
 129        }
 130
 131        /// <summary>
 132        /// Attempt to parse a partial Uri string into a Uri object.
 133        /// The string may contain the scheme, user info, host, and port. The host is the only required part.
 134        /// Example expected inputs: contoso.com, contoso.com:8080, http://contoso.com/, user@contoso.com.
 135        /// </summary>
 136        /// <returns><see langword="null"/> if parsing fails.</returns>
 137        private static unsafe Uri? GetUriFromString(string? value)
 138        {
 139            if (string.IsNullOrEmpty(value))
 140            {
 141                return null;
 142            }
 143
 144            int hostIndex = 0;
 145            string protocol = "http";
 146            ushort port = 80;
 147
 148            if (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
 149            {
 150                hostIndex = 7;
 151            }
 152            else if (value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
 153            {
 154                hostIndex = 8;
 155                protocol = "https";
 156                port = 443;
 157            }
 158            else if (value.StartsWith("socks4://", StringComparison.OrdinalIgnoreCase))
 159            {
 160                hostIndex = 9;
 161                protocol = "socks4";
 162            }
 163            else if (value.StartsWith("socks5://", StringComparison.OrdinalIgnoreCase))
 164            {
 165                hostIndex = 9;
 166                protocol = "socks5";
 167            }
 168            else if (value.StartsWith("socks5h://", StringComparison.OrdinalIgnoreCase))
 169            {
 170                hostIndex = 10;
 171                protocol = "socks5h";
 172            }
 173            else if (value.StartsWith("socks4a://", StringComparison.OrdinalIgnoreCase))
 174            {
 175                hostIndex = 10;
 176                protocol = "socks4a";
 177            }
 178
 179            if (hostIndex > 0)
 180            {
 181                value = value.Substring(hostIndex);
 182            }
 183
 184            string? user = null;
 185            string? password = null;
 186            string host;
 187
 188            // Check if there is authentication part with user and possibly password.
 189            // Curl accepts raw text and that may break URI parser.
 190            int separatorIndex = value.LastIndexOf('@');
 191            if (separatorIndex != -1)
 192            {
 193                // The User and password may or may not be URL encoded.
 194                // Curl seems to accept both. To match that, we also decode the value.
 195                string auth = Uri.UnescapeDataString(value.AsSpan(0, separatorIndex));
 196
 197                value = value.Substring(separatorIndex + 1);
 198                separatorIndex = auth.IndexOf(':');
 199                if (separatorIndex == -1)
 200                {
 201                    user = auth;
 202                }
 203                else
 204                {
 205                    user = auth.Substring(0, separatorIndex);
 206                    password = auth.Substring(separatorIndex + 1);
 207                }
 208            }
 209
 210            // We expect inputs to not contain the path/query/fragment, but we do handle some simple cases like a traili
 211            // An arbitrary path can break this parsing logic, but we expect environment variables to be trusted and wel
 212            int delimiterIndex = value.IndexOfAny('/', '?', '#');
 213            if (delimiterIndex >= 0)
 214            {
 215                value = value.Substring(0, delimiterIndex);
 216            }
 217
 218            int ipV6AddressEnd = value.IndexOf(']');
 219            separatorIndex = value.LastIndexOf(':');
 220            // No ':' or it is part of IPv6 address.
 221            if (separatorIndex == -1 || (ipV6AddressEnd != -1 && separatorIndex < ipV6AddressEnd))
 222            {
 223                host = value;
 224            }
 225            else
 226            {
 227                host = value.Substring(0, separatorIndex);
 228
 229                if (!ushort.TryParse(value.AsSpan(separatorIndex + 1), out port))
 230                {
 231                    return null;
 232                }
 233            }
 234
 235            try
 236            {
 237                UriBuilder ub = new UriBuilder(protocol, host, port);
 238                if (user != null)
 239                {
 240                    ub.UserName = Uri.EscapeDataString(user);
 241                }
 242
 243                if (password != null)
 244                {
 245                    ub.Password = Uri.EscapeDataString(password);
 246                }
 247
 248                Uri uri = ub.Uri;
 249
 250                // if both user and password exist and are empty we should preserve that and use default credentials.
 251                // UriBuilder does not handle that now e.g. does not distinguish between empty and missing.
 252                if (user == "" && password == "")
 253                {
 254                    Span<Range> tokens = stackalloc Range[3];
 255                    ReadOnlySpan<char> uriSpan = uri.ToString();
 256                    if (uriSpan.Split(tokens, '/') == 3)
 257                    {
 258                        uri = new Uri($"{uriSpan[tokens[0]]}//:@{uriSpan[tokens[2]]}");
 259                    }
 260                }
 261
 262                return uri;
 263            }
 264            catch { };
 265            return null;
 266        }
 267
 268        /// <summary>
 269        /// This function returns true if given Host match bypass list.
 270        /// Note, that the list is common for http and https.
 271        /// </summary>
 272        private bool IsMatchInBypassList(Uri input)
 273        {
 274            if (_bypass != null)
 275            {
 276                foreach (string s in _bypass)
 277                {
 278                    if (s[0] == '.')
 279                    {
 280                        // This should match either domain it self or any subdomain or host
 281                        // .foo.com will match foo.com it self or *.foo.com
 282                        if (s.AsSpan(1).Equals(input.Host, StringComparison.OrdinalIgnoreCase))
 283                        {
 284                            return true;
 285                        }
 286                        else if (input.Host.EndsWith(s, StringComparison.OrdinalIgnoreCase))
 287                        {
 288                            return true;
 289                        }
 290
 291                    }
 292                    else
 293                    {
 294                        if (string.Equals(s, input.Host, StringComparison.OrdinalIgnoreCase))
 295                        {
 296                            return true;
 297                        }
 298                    }
 299                }
 300            }
 301
 302            if (_bypassNetworks != null && IPAddress.TryParse(input.Host, out IPAddress? ip))
 303            {
 304                foreach (IPNetwork network in _bypassNetworks)
 305                {
 306                    if (network.Contains(ip))
 307                    {
 308                        return true;
 309                    }
 310                }
 311            }
 312
 313            return false;
 314        }
 315
 316        /// <summary>
 317        /// Gets the proxy URI. (iWebProxy interface)
 318        /// </summary>
 319        public Uri? GetProxy(Uri uri)
 320        {
 321            return HttpUtilities.IsSupportedNonSecureScheme(uri.Scheme) ? _httpProxyUri : _httpsProxyUri;
 322        }
 323
 324        /// <summary>
 325        /// Checks if URI is subject to proxy or not.
 326        /// </summary>
 327        public bool IsBypassed(Uri uri)
 328        {
 329            return GetProxy(uri) == null ? true : IsMatchInBypassList(uri);
 330        }
 331
 332        public ICredentials? Credentials
 333        {
 334            get
 335            {
 336                return _credentials;
 337            }
 338            set
 339            {
 340                _credentials = value;
 341            }
 342        }
 343    }
 344}
 345