< Summary

Information
Class: System.Net.Http.MultiProxy
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\MultiProxy.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 155
Coverable lines: 155
Total lines: 277
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 52
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%
.ctor(...)100%110%
ParseManualSettings(...)0%220%
CreateLazy(...)0%220%
ReadNext(...)0%14140%
ReadNextHelper(...)0%880%
TryParseProxyConfigPart(...)0%26260%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\MultiProxy.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.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6
 7namespace System.Net.Http
 8{
 9    /// <summary>
 10    /// A collection of proxies.
 11    /// </summary>
 12    internal struct MultiProxy
 13    {
 14        private readonly FailedProxyCache? _failedProxyCache;
 15        private readonly Uri[]? _uris;
 16        private readonly string? _proxyConfig;
 17        private readonly bool _secure;
 18        private int _currentIndex;
 19        private Uri? _currentUri;
 20
 21        private MultiProxy(FailedProxyCache? failedProxyCache, Uri[] uris)
 022        {
 023            _failedProxyCache = failedProxyCache;
 024            _uris = uris;
 025            _proxyConfig = null;
 026            _secure = default;
 027            _currentIndex = 0;
 028            _currentUri = null;
 029        }
 30
 31        private MultiProxy(FailedProxyCache failedProxyCache, string proxyConfig, bool secure)
 032        {
 033            _failedProxyCache = failedProxyCache;
 034            _uris = null;
 035            _proxyConfig = proxyConfig;
 036            _secure = secure;
 037            _currentIndex = 0;
 038            _currentUri = null;
 039        }
 40
 041        public static MultiProxy Empty => new MultiProxy(null, Array.Empty<Uri>());
 42
 43        /// <summary>
 44        /// Parses a WinHTTP proxy config into a MultiProxy instance.
 45        /// </summary>
 46        /// <param name="failedProxyCache">The cache of failed proxy requests to employ.</param>
 47        /// <param name="proxyConfig">The WinHTTP proxy config to parse.</param>
 48        /// <param name="secure">If true, return proxies suitable for use with a secure connection. If false, return pro
 49        public static MultiProxy ParseManualSettings(FailedProxyCache failedProxyCache, string? proxyConfig, bool secure
 050        {
 051            Debug.Assert(failedProxyCache != null);
 52
 053            Uri[] uris = Array.Empty<Uri>();
 54
 055            ReadOnlySpan<char> span = proxyConfig;
 056            while (TryParseProxyConfigPart(span, secure, manualSettingsUsed: true, out Uri? uri, out int charactersConsu
 057            {
 058                int idx = uris.Length;
 59
 60                // Assume that we will typically not have more than 1...3 proxies, so just
 61                // grow by 1. This method is currently only used once per process, so the
 62                // case of an abnormally large config will not be much of a concern anyway.
 063                Array.Resize(ref uris, idx + 1);
 064                uris[idx] = uri;
 65
 066                span = span.Slice(charactersConsumed);
 067            }
 68
 069            return new MultiProxy(failedProxyCache, uris);
 070        }
 71
 72        /// <summary>
 73        /// Initializes a MultiProxy instance that lazily parses a given WinHTTP configuration string.
 74        /// </summary>
 75        /// <param name="failedProxyCache">The cache of failed proxy requests to employ.</param>
 76        /// <param name="proxyConfig">The WinHTTP proxy config to parse.</param>
 77        /// <param name="secure">If true, return proxies suitable for use with a secure connection. If false, return pro
 78        public static MultiProxy CreateLazy(FailedProxyCache failedProxyCache, string proxyConfig, bool secure)
 079        {
 080            Debug.Assert(failedProxyCache != null);
 81
 082            return !string.IsNullOrEmpty(proxyConfig) ?
 083                new MultiProxy(failedProxyCache, proxyConfig, secure) :
 084                MultiProxy.Empty;
 085        }
 86
 87        /// <summary>
 88        /// Reads the next proxy URI from the MultiProxy.
 89        /// </summary>
 90        /// <param name="uri">The next proxy to use for the request.</param>
 91        /// <param name="isFinalProxy">If true, indicates there are no further proxies to read from the config.</param>
 92        /// <returns>If there is a proxy available, true. Otherwise, false.</returns>
 93        public bool ReadNext([NotNullWhen(true)] out Uri? uri, out bool isFinalProxy)
 094        {
 95            // Enumerating indicates the previous proxy has failed; mark it as such.
 096            if (_currentUri != null)
 097            {
 098                Debug.Assert(_failedProxyCache != null);
 099                _failedProxyCache.SetProxyFailed(_currentUri);
 0100            }
 101
 102            // If no more proxies to read, return out quickly.
 0103            if (!ReadNextHelper(out uri, out isFinalProxy))
 0104            {
 0105                _currentUri = null;
 0106                return false;
 107            }
 108
 109            // If this is the first ReadNext() and all proxies are marked as failed, return the proxy that is closest to
 0110            Uri? oldestFailedProxyUri = null;
 0111            long oldestFailedProxyTicks = long.MaxValue;
 112
 113            do
 0114            {
 0115                Debug.Assert(_failedProxyCache != null);
 0116                long renewTicks = _failedProxyCache.GetProxyRenewTicks(uri);
 117
 118                // Proxy hasn't failed recently, return for use.
 0119                if (renewTicks == FailedProxyCache.Immediate)
 0120                {
 0121                    _currentUri = uri;
 0122                    return true;
 123                }
 124
 0125                if (renewTicks < oldestFailedProxyTicks)
 0126                {
 0127                    oldestFailedProxyUri = uri;
 0128                    oldestFailedProxyTicks = renewTicks;
 0129                }
 0130            }
 0131            while (ReadNextHelper(out uri, out isFinalProxy));
 132
 133            // All the proxies in the config have failed; in this case, return the proxy that is closest to renewal.
 0134            if (_currentUri == null)
 0135            {
 0136                uri = oldestFailedProxyUri;
 0137                _currentUri = oldestFailedProxyUri;
 138
 0139                if (oldestFailedProxyUri != null)
 0140                {
 0141                    Debug.Assert(uri != null);
 0142                    _failedProxyCache.TryRenewProxy(uri, oldestFailedProxyTicks);
 0143                    return true;
 144                }
 0145            }
 146
 0147            return false;
 0148        }
 149
 150        /// <summary>
 151        /// Reads the next proxy URI from the MultiProxy, either via parsing a config string or from an array.
 152        /// </summary>
 153        private bool ReadNextHelper([NotNullWhen(true)] out Uri? uri, out bool isFinalProxy)
 0154        {
 0155            Debug.Assert(_uris != null || _proxyConfig != null, $"{nameof(ReadNext)} must not be called on a default-ini
 156
 0157            if (_uris != null)
 0158            {
 0159                if (_currentIndex == _uris.Length)
 0160                {
 0161                    uri = default;
 0162                    isFinalProxy = default;
 0163                    return false;
 164                }
 165
 0166                uri = _uris[_currentIndex++];
 0167                isFinalProxy = _currentIndex == _uris.Length;
 0168                return true;
 169            }
 170
 0171            Debug.Assert(_proxyConfig != null);
 0172            if (_currentIndex < _proxyConfig.Length)
 0173            {
 0174                bool hasProxy = TryParseProxyConfigPart(_proxyConfig.AsSpan(_currentIndex), _secure, manualSettingsUsed:
 175
 0176                _currentIndex += charactersConsumed;
 0177                Debug.Assert(_currentIndex <= _proxyConfig.Length);
 178
 0179                isFinalProxy = _currentIndex == _proxyConfig.Length;
 180
 0181                return hasProxy;
 182            }
 183
 0184            uri = default;
 0185            isFinalProxy = default;
 0186            return false;
 0187        }
 188
 189        /// <summary>
 190        /// This method is used to parse WinINet Proxy strings, a single proxy at a time.
 191        /// </summary>
 192        /// <remarks>
 193        /// The strings are a semicolon or whitespace separated list, with each entry in the following format:
 194        /// ([&lt;scheme&gt;=][&lt;scheme&gt;"://"]&lt;server&gt;[":"&lt;port&gt;])
 195        /// </remarks>
 196        private static bool TryParseProxyConfigPart(ReadOnlySpan<char> proxyString, bool secure, bool manualSettingsUsed
 0197        {
 198            const int SECURE_FLAG = 1;
 199            const int INSECURE_FLAG = 2;
 200            const string ProxyDelimiters = "; \n\r\t";
 201
 0202            int wantedFlag = secure ? SECURE_FLAG : INSECURE_FLAG;
 0203            int originalLength = proxyString.Length;
 204
 0205            while (true)
 0206            {
 207                // Skip any delimiters.
 0208                int iter = 0;
 0209                while (iter < proxyString.Length && ProxyDelimiters.Contains(proxyString[iter]))
 0210                {
 0211                    ++iter;
 0212                }
 213
 0214                if (iter == proxyString.Length)
 0215                {
 0216                    break;
 217                }
 218
 0219                proxyString = proxyString.Slice(iter);
 220
 221                // Determine which scheme this part is for.
 222                // If no schema is defined, use both.
 0223                int proxyType = SECURE_FLAG | INSECURE_FLAG;
 224
 0225                if (proxyString.StartsWith("http="))
 0226                {
 0227                    proxyType = INSECURE_FLAG;
 0228                    proxyString = proxyString.Slice("http=".Length);
 0229                }
 0230                else if (proxyString.StartsWith("https="))
 0231                {
 0232                    proxyType = SECURE_FLAG;
 0233                    proxyString = proxyString.Slice("https=".Length);
 0234                }
 235
 0236                if (proxyString.StartsWith("http://"))
 0237                {
 0238                    if (!manualSettingsUsed)
 0239                    {
 0240                        proxyType = INSECURE_FLAG;
 0241                    }
 0242                    proxyString = proxyString.Slice("http://".Length);
 0243                }
 0244                else if (proxyString.StartsWith("https://"))
 0245                {
 0246                    if (!manualSettingsUsed)
 0247                    {
 0248                        proxyType = SECURE_FLAG;
 0249                    }
 0250                    proxyString = proxyString.Slice("https://".Length);
 0251                }
 252
 253                // Find the next delimiter, or end of string.
 0254                iter = proxyString.IndexOfAny(ProxyDelimiters);
 0255                if (iter < 0)
 0256                {
 0257                    iter = proxyString.Length;
 0258                }
 259
 260                // Return URI if it's a match to what we want.
 0261                if ((proxyType & wantedFlag) != 0 && Uri.TryCreate(string.Concat("http://", proxyString.Slice(0, iter)),
 0262                {
 0263                    charactersConsumed = originalLength - proxyString.Length + iter;
 0264                    Debug.Assert(charactersConsumed > 0);
 265
 0266                    return true;
 267                }
 268
 0269                proxyString = proxyString.Slice(iter);
 0270            }
 271
 0272            uri = null;
 0273            charactersConsumed = originalLength;
 0274            return false;
 0275        }
 276    }
 277}