< Summary

Information
Class: System.Net.Http.WinInetProxyHelper
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\WinInetProxyHelper.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 109
Coverable lines: 109
Total lines: 197
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 50
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%
GetProxyForUrl(...)0%36360%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\WinInetProxyHelper.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;
 5using System.Runtime.InteropServices;
 6
 7using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle;
 8
 9namespace System.Net.Http
 10{
 11    // This class is only used on OS versions where WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
 12    // is not supported (i.e. before Win8.1/Win2K12R2) in the WinHttpOpen() function.
 13    internal sealed class WinInetProxyHelper
 14    {
 15        private const int RecentAutoDetectionInterval = 120_000; // 2 minutes in milliseconds.
 16        private readonly string? _autoConfigUrl, _proxy, _proxyBypass;
 17        private readonly bool _autoDetect;
 18        private readonly bool _useProxy;
 19        private bool _autoDetectionFailed;
 20        private int _lastTimeAutoDetectionFailed; // Environment.TickCount units (milliseconds).
 21
 022        public WinInetProxyHelper()
 023        {
 024            Interop.WinHttp.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = default;
 25
 26            try
 027            {
 028                if (Interop.WinHttp.WinHttpGetIEProxyConfigForCurrentUser(out proxyConfig))
 029                {
 030                    _autoConfigUrl = Marshal.PtrToStringUni(proxyConfig.AutoConfigUrl)!;
 031                    _autoDetect = proxyConfig.AutoDetect != 0;
 032                    _proxy = Marshal.PtrToStringUni(proxyConfig.Proxy)!;
 033                    _proxyBypass = Marshal.PtrToStringUni(proxyConfig.ProxyBypass)!;
 34
 035                    if (NetEventSource.Log.IsEnabled())
 036                    {
 037                        NetEventSource.Info(this, $"AutoConfigUrl={AutoConfigUrl}, AutoDetect={AutoDetect}, Proxy={Proxy
 038                    }
 39
 040                    _useProxy = true;
 041                }
 42                else
 043                {
 44                    // We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
 045                    int lastError = Marshal.GetLastWin32Error();
 046                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"error={lastError}");
 047                }
 48
 049                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"_useProxy={_useProxy}");
 050            }
 51
 52            finally
 053            {
 54                // FreeHGlobal already checks for null pointer before freeing the memory.
 055                Marshal.FreeHGlobal(proxyConfig.AutoConfigUrl);
 056                Marshal.FreeHGlobal(proxyConfig.Proxy);
 057                Marshal.FreeHGlobal(proxyConfig.ProxyBypass);
 058            }
 059        }
 60
 061        public string? AutoConfigUrl => _autoConfigUrl;
 62
 063        public bool AutoDetect => _autoDetect;
 64
 065        public bool AutoSettingsUsed => AutoDetect || !string.IsNullOrEmpty(AutoConfigUrl);
 66
 067        public bool ManualSettingsUsed => !string.IsNullOrEmpty(Proxy);
 68
 069        public bool ManualSettingsOnly => !AutoSettingsUsed && ManualSettingsUsed;
 70
 071        public string? Proxy => _proxy;
 72
 073        public string? ProxyBypass => _proxyBypass;
 74
 75        public bool RecentAutoDetectionFailure =>
 076            _autoDetectionFailed &&
 077            Environment.TickCount - _lastTimeAutoDetectionFailed <= RecentAutoDetectionInterval;
 78
 79        public bool GetProxyForUrl(
 80            SafeWinHttpHandle? sessionHandle,
 81            Uri uri,
 82            out Interop.WinHttp.WINHTTP_PROXY_INFO proxyInfo)
 083        {
 084            proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY;
 085            proxyInfo.Proxy = IntPtr.Zero;
 086            proxyInfo.ProxyBypass = IntPtr.Zero;
 87
 088            if (!_useProxy)
 089            {
 090                return false;
 91            }
 92
 093            bool useProxy = false;
 94
 95            Interop.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
 096            autoProxyOptions.AutoConfigUrl = AutoConfigUrl;
 097            autoProxyOptions.AutoDetectFlags = AutoDetect ?
 098                (Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DHCP | Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DNS_A) : 0;
 099            autoProxyOptions.AutoLoginIfChallenged = false;
 0100            autoProxyOptions.Flags =
 0101                (AutoDetect ? Interop.WinHttp.WINHTTP_AUTOPROXY_AUTO_DETECT : 0) |
 0102                (!string.IsNullOrEmpty(AutoConfigUrl) ? Interop.WinHttp.WINHTTP_AUTOPROXY_CONFIG_URL : 0);
 0103            autoProxyOptions.Reserved1 = IntPtr.Zero;
 0104            autoProxyOptions.Reserved2 = 0;
 105
 106            // AutoProxy Cache.
 107            // https://learn.microsoft.com/windows/desktop/WinHttp/autoproxy-cache
 108            // If the out-of-process service is active when WinHttpGetProxyForUrl is called, the cached autoproxy
 109            // URL and script are available to the whole computer. However, if the out-of-process service is used,
 110            // and the fAutoLogonIfChallenged flag in the pAutoProxyOptions structure is true, then the autoproxy
 111            // URL and script are not cached. Therefore, calling WinHttpGetProxyForUrl with the fAutoLogonIfChallenged
 112            // member set to TRUE results in additional overhead operations that may affect performance.
 113            // The following steps can be used to improve performance:
 114            // 1. Call WinHttpGetProxyForUrl with the fAutoLogonIfChallenged parameter set to false. The autoproxy
 115            //    URL and script are cached for future calls to WinHttpGetProxyForUrl.
 116            // 2. If Step 1 fails, with ERROR_WINHTTP_LOGIN_FAILURE, then call WinHttpGetProxyForUrl with the
 117            //    fAutoLogonIfChallenged member set to TRUE.
 118            //
 119            // We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
 120
 121#pragma warning disable CA1845 // file is shared with a build that lacks string.Concat for spans
 122            // Underlying code does not understand WebSockets so we need to convert it to http or https.
 0123            string destination = uri.AbsoluteUri;
 0124            if (uri.Scheme == UriScheme.Wss)
 0125            {
 0126                destination = UriScheme.Https + destination.Substring(UriScheme.Wss.Length);
 0127            }
 0128            else if (uri.Scheme == UriScheme.Ws)
 0129            {
 0130                destination = UriScheme.Http + destination.Substring(UriScheme.Ws.Length);
 0131            }
 132#pragma warning restore CA1845
 133
 0134            var repeat = false;
 135            do
 0136            {
 0137                _autoDetectionFailed = false;
 0138                if (Interop.WinHttp.WinHttpGetProxyForUrl(
 0139                    sessionHandle!,
 0140                    destination,
 0141                    ref autoProxyOptions,
 0142                    out proxyInfo))
 0143                {
 0144                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Using autoconfig proxy settings");
 0145                    useProxy = true;
 146
 0147                    break;
 148                }
 149                else
 0150                {
 0151                    var lastError = Marshal.GetLastWin32Error();
 0152                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"error={lastError}");
 153
 0154                    if (lastError == Interop.WinHttp.ERROR_WINHTTP_LOGIN_FAILURE)
 0155                    {
 0156                        if (repeat)
 0157                        {
 158                            // We don't retry more than once.
 0159                            break;
 160                        }
 161                        else
 0162                        {
 0163                            repeat = true;
 0164                            autoProxyOptions.AutoLoginIfChallenged = true;
 0165                        }
 0166                    }
 167                    else
 0168                    {
 0169                        if (lastError == Interop.WinHttp.ERROR_WINHTTP_AUTODETECTION_FAILED)
 0170                        {
 0171                            _autoDetectionFailed = true;
 0172                            _lastTimeAutoDetectionFailed = Environment.TickCount;
 0173                        }
 174
 0175                        break;
 176                    }
 0177                }
 0178            } while (repeat);
 179
 180            // Fall back to manual settings if available.
 0181            if (!useProxy && !string.IsNullOrEmpty(Proxy))
 0182            {
 0183                proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NAMED_PROXY;
 0184                proxyInfo.Proxy = Marshal.StringToHGlobalUni(Proxy);
 0185                proxyInfo.ProxyBypass = string.IsNullOrEmpty(ProxyBypass) ?
 0186                    IntPtr.Zero : Marshal.StringToHGlobalUni(ProxyBypass);
 187
 0188                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Fallback to Proxy={Proxy}, ProxyBypass={
 0189                useProxy = true;
 0190            }
 191
 0192            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"useProxy={useProxy}");
 193
 0194            return useProxy;
 0195        }
 196    }
 197}