< Summary

Information
Class: System.Net.Http.HttpWindowsProxy
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpWindowsProxy.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 211
Coverable lines: 211
Total lines: 375
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 136
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(...)0%880%
RegistryChangeNotificationCallback(...)0%440%
UpdateConfiguration(...)0%68680%
Dispose()0%12120%
GetProxy(...)0%440%
GetMultiProxy(...)0%38380%
IsSecureUri(...)0%220%
IsBypassed(...)100%110%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpWindowsProxy.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;
 5using System.Collections.Concurrent;
 6using System.Collections.Generic;
 7using System.Diagnostics;
 8using System.Diagnostics.CodeAnalysis;
 9using System.Net.NetworkInformation;
 10using System.Runtime.InteropServices;
 11using System.Text;
 12using System.Threading;
 13using Microsoft.Win32;
 14using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle;
 15
 16namespace System.Net.Http
 17{
 18    internal sealed class HttpWindowsProxy : IMultiWebProxy, IDisposable
 19    {
 020        private readonly RegistryKey? _internetSettingsRegistry = Registry.CurrentUser?.OpenSubKey("Software\\Microsoft\
 21        private MultiProxy _insecureProxy;      // URI of the http system proxy if set
 22        private MultiProxy _secureProxy;       // URI of the https system proxy if set
 023        private FailedProxyCache _failedProxies = new FailedProxyCache();
 24        private List<string>? _bypass;          // list of domains not to proxy
 25        private List<IPAddress>? _localIp;
 26        private ICredentials? _credentials;
 27        private WinInetProxyHelper _proxyHelper;
 28        private SafeWinHttpHandle? _sessionHandle;
 29        private bool _disposed;
 030        private EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
 31        private const int RegistrationFlags = Interop.Advapi32.REG_NOTIFY_CHANGE_NAME | Interop.Advapi32.REG_NOTIFY_CHAN
 32        private RegisteredWaitHandle? _registeredWaitHandle;
 33
 34        // 'proxy' used from tests via Reflection
 035        public HttpWindowsProxy(WinInetProxyHelper? proxy = null)
 036        {
 37
 038            if (_internetSettingsRegistry != null && proxy == null)
 039            {
 40                // we register for change notifications so we can react to changes during lifetime.
 041                if (Interop.Advapi32.RegNotifyChangeKeyValue(_internetSettingsRegistry.Handle, true, RegistrationFlags, 
 042                {
 043                    _registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(_waitHandle, RegistryChangeNotificati
 044                }
 045            }
 46
 047            UpdateConfiguration(proxy);
 048        }
 49
 50        private static void RegistryChangeNotificationCallback(object? state, bool timedOut)
 051        {
 052            HttpWindowsProxy proxy = (HttpWindowsProxy)state!;
 053            if (!proxy._disposed)
 054            {
 55
 56                // This is executed from threadpool. we should not ever throw here.
 57                try
 058                {
 59                    // We need to register for notification every time. We regisrerand lock before we process configurat
 60                    // so if there is update it would be serialized to ensure consistency.
 061                    Interop.Advapi32.RegNotifyChangeKeyValue(proxy._internetSettingsRegistry!.Handle, true, Registration
 062                    lock (proxy)
 063                    {
 064                        proxy.UpdateConfiguration();
 065                    }
 066                }
 067                catch (Exception ex)
 068                {
 069                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(proxy, $"Failed to refresh proxy configurat
 070                }
 071            }
 072        }
 73
 74        [MemberNotNull(nameof(_proxyHelper))]
 75        private void UpdateConfiguration(WinInetProxyHelper? proxyHelper = null)
 076        {
 77
 078            proxyHelper ??= new WinInetProxyHelper();
 79
 080            if (proxyHelper.AutoSettingsUsed)
 081            {
 082                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(proxyHelper, $"AutoSettingsUsed, calling {nameof
 083                SafeWinHttpHandle? sessionHandle = Interop.WinHttp.WinHttpOpen(
 084                    IntPtr.Zero,
 085                    Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY,
 086                    Interop.WinHttp.WINHTTP_NO_PROXY_NAME,
 087                    Interop.WinHttp.WINHTTP_NO_PROXY_BYPASS,
 088                    (int)Interop.WinHttp.WINHTTP_FLAG_ASYNC);
 89
 090                if (sessionHandle.IsInvalid)
 091                {
 92                    // Proxy failures are currently ignored by managed handler.
 093                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(proxyHelper, $"{nameof(Interop.WinHttp.WinH
 094                    sessionHandle.Dispose();
 095                }
 96
 097                _sessionHandle = sessionHandle;
 098            }
 99
 0100            if (proxyHelper.ManualSettingsUsed)
 0101            {
 0102                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(proxyHelper, $"ManualSettingsUsed, {proxyHelper.
 103
 0104                _secureProxy = MultiProxy.ParseManualSettings(_failedProxies, proxyHelper.Proxy, true);
 0105                _insecureProxy = MultiProxy.ParseManualSettings(_failedProxies, proxyHelper.Proxy, false);
 106
 0107                if (!string.IsNullOrWhiteSpace(proxyHelper.ProxyBypass))
 0108                {
 0109                    int idx = 0;
 110                    string? tmp;
 0111                    bool bypassLocal = false;
 0112                    List<IPAddress>? localIp = null;
 113
 114                    // Process bypass list for manual setting.
 115                    // Initial list size is best guess based on string length assuming each entry is at least 5 characte
 0116                    List<string>? bypass = new List<string>(proxyHelper.ProxyBypass.Length / 5);
 117
 0118                    while (idx < proxyHelper.ProxyBypass.Length)
 0119                    {
 120                        // Strip leading spaces and scheme if any.
 0121                        while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ' ') { idx += 1; 
 0122                        if (string.Compare(proxyHelper.ProxyBypass, idx, "http://", 0, 7, StringComparison.OrdinalIgnore
 0123                        {
 0124                            idx += 7;
 0125                        }
 0126                        else if (string.Compare(proxyHelper.ProxyBypass, idx, "https://", 0, 8, StringComparison.Ordinal
 0127                        {
 0128                            idx += 8;
 0129                        }
 130
 0131                        if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == '[')
 0132                        {
 133                            // Strip [] from IPv6 so we can use IdnHost laster for matching.
 0134                            idx += 1;
 0135                        }
 136
 0137                        int start = idx;
 0138                        while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ' ' && proxyHelpe
 139
 0140                        if (idx == start)
 0141                        {
 142                            // Empty string.
 0143                            tmp = null;
 0144                        }
 0145                        else if (string.Compare(proxyHelper.ProxyBypass, start, "<local>", 0, 7, StringComparison.Ordina
 0146                        {
 0147                            bypassLocal = true;
 0148                            tmp = null;
 0149                        }
 150                        else
 0151                        {
 0152                            tmp = proxyHelper.ProxyBypass.Substring(start, idx - start);
 0153                        }
 154
 155                        // Skip trailing characters if any.
 0156                        if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';')
 0157                        {
 158                            // Got stopped at space or ']'. Strip until next ';' or end.
 0159                            while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';') { idx +=
 0160                        }
 0161                        if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ';')
 0162                        {
 0163                            idx++;
 0164                        }
 0165                        if (tmp == null)
 0166                        {
 0167                            continue;
 168                        }
 169
 0170                        bypass.Add(tmp);
 0171                    }
 172
 0173                    _bypass = bypass.Count > 0 ? bypass : null;
 174
 0175                    if (bypassLocal)
 0176                    {
 0177                        localIp = new List<IPAddress>();
 0178                        foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
 0179                        {
 0180                            IPInterfaceProperties ipProps = netInterface.GetIPProperties();
 0181                            foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
 0182                            {
 0183                                localIp.Add(addr.Address);
 0184                            }
 0185                        }
 0186                    }
 187
 0188                    _localIp = localIp?.Count > 0 ? localIp : null;
 0189                }
 0190            }
 191
 0192            _proxyHelper = proxyHelper;
 0193        }
 194
 195        public void Dispose()
 0196        {
 0197            if (!_disposed)
 0198            {
 0199                _disposed = true;
 200
 0201                if (_sessionHandle != null && !_sessionHandle.IsInvalid)
 0202                {
 0203                    SafeWinHttpHandle.DisposeAndClearHandle(ref _sessionHandle);
 0204                }
 205
 0206                _waitHandle?.Dispose();
 0207                _internetSettingsRegistry?.Dispose();
 0208                _registeredWaitHandle?.Unregister(null);
 0209            }
 0210        }
 211
 212        /// <summary>
 213        /// Gets the proxy URI. (IWebProxy interface)
 214        /// </summary>
 215        public Uri? GetProxy(Uri uri)
 0216        {
 0217            if (!_proxyHelper.AutoSettingsUsed && !_proxyHelper.ManualSettingsOnly)
 0218            {
 0219                return null;
 220            }
 221
 0222            GetMultiProxy(uri).ReadNext(out Uri? proxyUri, out _);
 0223            return proxyUri;
 0224        }
 225
 226        /// <summary>
 227        /// Gets the proxy URIs.
 228        /// </summary>
 229        public MultiProxy GetMultiProxy(Uri uri)
 0230        {
 231            // We need WinHTTP to detect and/or process a PAC (JavaScript) file. This maps to
 232            // "Automatically detect settings" and/or "Use automatic configuration script" from IE
 233            // settings. But, calling into WinHTTP can be slow especially when it has to call into
 234            // the out-of-process service to discover, load, and run the PAC file. So, we skip
 235            // calling into WinHTTP if there was a recent failure to detect a PAC file on the network.
 236            // This is a common error. The default IE settings on a Windows machine consist of the
 237            // single checkbox for "Automatically detect settings" turned on and most networks
 238            // won't actually discover a PAC file on the network since WPAD protocol isn't configured.
 0239            if (_proxyHelper.AutoSettingsUsed && !_proxyHelper.RecentAutoDetectionFailure)
 0240            {
 0241                Interop.WinHttp.WINHTTP_PROXY_INFO proxyInfo = default;
 242                try
 0243                {
 0244                    if (_proxyHelper.GetProxyForUrl(_sessionHandle, uri, out proxyInfo))
 0245                    {
 246                        // If WinHTTP just specified a Proxy with no ProxyBypass list, then
 247                        // we can return the Proxy uri directly.
 0248                        if (proxyInfo.ProxyBypass == IntPtr.Zero)
 0249                        {
 0250                            if (proxyInfo.Proxy != IntPtr.Zero)
 0251                            {
 0252                                string proxyStr = Marshal.PtrToStringUni(proxyInfo.Proxy)!;
 253
 0254                                return MultiProxy.CreateLazy(_failedProxies, proxyStr, IsSecureUri(uri));
 255                            }
 256                            else
 0257                            {
 0258                                return MultiProxy.Empty;
 259                            }
 260                        }
 261
 262                        // A bypass list was also specified. This means that WinHTTP has fallen back to
 263                        // using the manual IE settings specified and there is a ProxyBypass list also.
 264                        // Since we're not really using the full WinHTTP stack, we need to use HttpSystemProxy
 265                        // to do the computation of the final proxy uri merging the information from the Proxy
 266                        // and ProxyBypass strings.
 0267                    }
 268                    else
 0269                    {
 0270                        return MultiProxy.Empty;
 271                    }
 0272                }
 273                finally
 0274                {
 0275                    Marshal.FreeHGlobal(proxyInfo.Proxy);
 0276                    Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
 0277                }
 0278            }
 279
 280            // Fallback to manual settings if present.
 0281            if (_proxyHelper.ManualSettingsUsed)
 0282            {
 0283                if (_localIp != null)
 0284                {
 285                    IPAddress? address;
 286
 0287                    if (uri.IsLoopback)
 0288                    {
 289                        // This is optimization for loopback addresses.
 290                        // Unfortunately this does not work for all local addresses.
 0291                        return MultiProxy.Empty;
 292                    }
 293
 294                    // Pre-Check if host may be IP address to avoid parsing.
 0295                    if (uri.HostNameType == UriHostNameType.IPv6 || uri.HostNameType == UriHostNameType.IPv4)
 0296                    {
 297                        // RFC1123 allows labels to start with number.
 298                        // Leading number may or may not be IP address.
 299                        // IPv6 [::1] notation. '[' is not valid character in names.
 0300                        if (IPAddress.TryParse(uri.IdnHost, out address))
 0301                        {
 302                            // Host is valid IP address.
 303                            // Check if it belongs to local system.
 0304                            foreach (IPAddress a in _localIp)
 0305                            {
 0306                                if (a.Equals(address))
 0307                                {
 0308                                    return MultiProxy.Empty;
 309                                }
 0310                            }
 0311                        }
 0312                    }
 0313                    if (uri.HostNameType != UriHostNameType.IPv6 && !uri.IdnHost.Contains('.'))
 0314                    {
 315                        // Not address and does not have a dot.
 316                        // Hosts without FQDN are considered local.
 0317                        return MultiProxy.Empty;
 318                    }
 0319                }
 320
 321                // Check if we have other rules for bypass.
 0322                if (_bypass != null)
 0323                {
 0324                    foreach (string entry in _bypass)
 0325                    {
 326                        // IdnHost does not have [].
 0327                        if (SimpleRegex.IsMatchWithStarWildcard(uri.IdnHost, entry))
 0328                        {
 0329                            return MultiProxy.Empty;
 330                        }
 0331                    }
 0332                }
 333
 334                // We did not find match on bypass list.
 0335                return IsSecureUri(uri) ? _secureProxy : _insecureProxy;
 336            }
 337
 0338            return MultiProxy.Empty;
 0339        }
 340
 341        private static bool IsSecureUri(Uri uri)
 0342        {
 0343            return uri.Scheme == UriScheme.Https || uri.Scheme == UriScheme.Wss;
 0344        }
 345
 346        /// <summary>
 347        /// Checks if URI is subject to proxy or not.
 348        /// </summary>
 349        public bool IsBypassed(Uri uri)
 0350        {
 351            // This HttpSystemProxy class is only consumed by SocketsHttpHandler and is not exposed outside of
 352            // SocketsHttpHandler. The current pattern for consumption of IWebProxy is to call IsBypassed first.
 353            // If it returns false, then the caller will call GetProxy. For this proxy implementation, computing
 354            // the return value for IsBypassed is as costly as calling GetProxy. We want to avoid doing extra
 355            // work. So, this proxy implementation for the IsBypassed method can always return false. Then the
 356            // GetProxy method will return non-null for a proxy, or null if no proxy should be used.
 0357            return false;
 0358        }
 359
 360        public ICredentials? Credentials
 361        {
 362            get
 0363            {
 0364                return _credentials;
 0365            }
 366            set
 0367            {
 0368                _credentials = value;
 0369            }
 370        }
 371
 372        // Access function for unit tests.
 373        internal List<string>? BypassList => _bypass;
 374    }
 375}