| File: System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs | Web Access |
| Project: src\runtime\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. 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> /// Attempt to parse a partial Uri string into a Uri object. /// The string may contain the scheme, user info, host, and port. The host is the only required part. /// Example expected inputs: contoso.com, contoso.com:8080, http://contoso.com/, user@contoso.com. /// </summary> /// <returns><see langword="null"/> if parsing fails.</returns> private static unsafe 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("socks5h://", StringComparison.OrdinalIgnoreCase)) { hostIndex = 10; protocol = "socks5h"; } 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); } } // We expect inputs to not contain the path/query/fragment, but we do handle some simple cases like a trailing slash. // An arbitrary path can break this parsing logic, but we expect environment variables to be trusted and well formed. int delimiterIndex = value.IndexOfAny('/', '?', '#'); if (delimiterIndex >= 0) { value = value.Substring(0, delimiterIndex); } 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); if (!ushort.TryParse(value.AsSpan(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.AsSpan(1).Equals(input.Host, StringComparison.OrdinalIgnoreCase)) { 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; } } } }