< Summary

Information
Class: System.Net.Http.ConnectHelper
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 92
Coverable lines: 92
Total lines: 166
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
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%
SetUpRemoteCertificateValidationCallback(...)0%440%
EstablishSslConnectionAsync()0%10100%
ConnectQuicAsync()100%110%
CreateWrappedException(...)0%220%
DeduceError(System.Exception)0%880%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.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.IO;
 6using System.Net.Quic;
 7using System.Net.Security;
 8using System.Net.Sockets;
 9using System.Runtime.Versioning;
 10using System.Security.Authentication;
 11using System.Security.Cryptography.X509Certificates;
 12using System.Threading;
 13using System.Threading.Tasks;
 14
 15namespace System.Net.Http
 16{
 17    internal static class ConnectHelper
 18    {
 19        /// <summary>
 20        /// Helper type used by HttpClientHandler when wrapping SocketsHttpHandler to map its
 21        /// certificate validation callback to the one used by SslStream.
 22        /// </summary>
 23        internal sealed class CertificateCallbackMapper
 24        {
 25            public readonly Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> FromHttpClien
 26            public readonly RemoteCertificateValidationCallback ForSocketsHttpHandler;
 27
 028            public CertificateCallbackMapper(Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bo
 029            {
 030                FromHttpClientHandler = fromHttpClientHandler;
 031                ForSocketsHttpHandler = (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors 
 032                    FromHttpClientHandler((HttpRequestMessage)sender, certificate as X509Certificate2, chain, sslPolicyE
 033            }
 34        }
 35
 36        private static SslClientAuthenticationOptions SetUpRemoteCertificateValidationCallback(SslClientAuthenticationOp
 037        {
 38            // If there's a cert validation callback, and if it came from HttpClientHandler,
 39            // wrap the original delegate in order to change the sender to be the request message (expected by HttpClien
 040            RemoteCertificateValidationCallback? callback = sslOptions.RemoteCertificateValidationCallback;
 041            if (callback != null && callback.Target is CertificateCallbackMapper mapper)
 042            {
 043                sslOptions = sslOptions.ShallowClone(); // Clone as we're about to mutate it and don't want to affect th
 044                Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> localFromHttpClientHandle
 045                HttpRequestMessage localRequest = request;
 046                sslOptions.RemoteCertificateValidationCallback = (object sender, X509Certificate? certificate, X509Chain
 047                {
 048                    Debug.Assert(localRequest != null);
 049                    bool result = localFromHttpClientHandler(localRequest, certificate as X509Certificate2, chain, sslPo
 050                    localRequest = null!; // ensure the SslOptions and this callback don't keep the first HttpRequestMes
 051                    return result;
 052                };
 053            }
 54
 055            return sslOptions;
 056        }
 57
 58        public static async ValueTask<SslStream> EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, 
 059        {
 060            sslOptions = SetUpRemoteCertificateValidationCallback(sslOptions, request);
 61
 062            SslStream sslStream = new SslStream(stream);
 63
 64            try
 065            {
 066                if (async)
 067                {
 068                    await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
 069                }
 70                else
 071                {
 072                    using (cancellationToken.UnsafeRegister(static s => ((Stream)s!).Dispose(), stream))
 073                    {
 074                        sslStream.AuthenticateAsClient(sslOptions);
 075                    }
 076                }
 077            }
 078            catch (Exception e)
 079            {
 080                sslStream.Dispose();
 81
 082                if (e is OperationCanceledException)
 083                {
 084                    throw;
 85                }
 86
 087                if (CancellationHelper.ShouldWrapInOperationCanceledException(e, cancellationToken))
 088                {
 089                    throw CancellationHelper.CreateOperationCanceledException(e, cancellationToken);
 90                }
 91
 092                HttpRequestException ex = new HttpRequestException(HttpRequestError.SecureConnectionError, SR.net_http_s
 093                if (request.IsExtendedConnectRequest)
 094                {
 95                    // Extended connect request is negotiating strictly for ALPN = "h2" because HttpClient is unaware of
 96                    // At this point, SSL connection for HTTP / 2 failed, and the exception should indicate the reason f
 097                    ex.Data["HTTP2_ENABLED"] = false;
 098                }
 099                throw ex;
 100            }
 101
 102            // Handle race condition if cancellation happens after SSL auth completes but before the registration is dis
 0103            if (cancellationToken.IsCancellationRequested)
 0104            {
 0105                sslStream.Dispose();
 0106                throw CancellationHelper.CreateOperationCanceledException(null, cancellationToken);
 107            }
 108
 0109            return sslStream;
 0110        }
 111
 112        [SupportedOSPlatform("linux")]
 113        [SupportedOSPlatform("macos")]
 114        [SupportedOSPlatform("windows")]
 115        public static async ValueTask<QuicConnection> ConnectQuicAsync(HttpRequestMessage request, DnsEndPoint endPoint,
 0116        {
 0117            clientAuthenticationOptions = SetUpRemoteCertificateValidationCallback(clientAuthenticationOptions, request)
 118
 119            try
 0120            {
 0121                return await QuicConnection.ConnectAsync(new QuicClientConnectionOptions()
 0122                {
 0123                    MaxInboundBidirectionalStreams = 0, // Client doesn't support inbound streams: https://www.rfc-edito
 0124                    MaxInboundUnidirectionalStreams = 5, // Minimum is 3: https://www.rfc-editor.org/rfc/rfc9114.html#un
 0125                    IdleTimeout = idleTimeout,
 0126                    DefaultStreamErrorCode = (long)Http3ErrorCode.RequestCancelled,
 0127                    DefaultCloseErrorCode = (long)Http3ErrorCode.NoError,
 0128                    RemoteEndPoint = endPoint,
 0129                    ClientAuthenticationOptions = clientAuthenticationOptions,
 0130                    StreamCapacityCallback = streamCapacityCallback,
 0131                }, cancellationToken).ConfigureAwait(false);
 132            }
 0133            catch (Exception ex) when (ex is not OperationCanceledException)
 0134            {
 0135                throw CreateWrappedException(ex, endPoint.Host, endPoint.Port, cancellationToken);
 136            }
 0137        }
 138
 139        internal static Exception CreateWrappedException(Exception exception, string host, int port, CancellationToken c
 0140        {
 0141            return CancellationHelper.ShouldWrapInOperationCanceledException(exception, cancellationToken) ?
 0142                CancellationHelper.CreateOperationCanceledException(exception, cancellationToken) :
 0143                new HttpRequestException(DeduceError(exception), $"{exception.Message} ({host}:{port})", exception, Requ
 144
 145            static HttpRequestError DeduceError(Exception exception)
 0146            {
 0147                if (exception is AuthenticationException)
 0148                {
 0149                    return HttpRequestError.SecureConnectionError;
 150                }
 151
 152                // Resolving a non-existent hostname often leads to EAI_AGAIN/TryAgain on Linux, indicating a non-author
 153                // Getting EAGAIN/TryAgain from a TCP connect() is not possible on Windows or Mac according to the docs 
 154                // which should be a very rare error in practice. As a result, mapping SocketError.TryAgain to HttpReque
 155                // leads to a more reliable distinction between NameResolutionError and ConnectionError.
 0156                if (exception is SocketException socketException &&
 0157                    socketException.SocketErrorCode is SocketError.HostNotFound or SocketError.TryAgain)
 0158                {
 0159                    return HttpRequestError.NameResolutionError;
 160                }
 161
 0162                return HttpRequestError.ConnectionError;
 0163            }
 0164        }
 165    }
 166}