File: System\ServiceModel\Channels\HttpChannelHelpers.cs
Web Access
Project: src\src\System.ServiceModel.Http\src\System.ServiceModel.Http.csproj (System.ServiceModel.Http)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Diagnostics.Contracts;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using System.Security.Policy;
using System.Security.Principal;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Text;
using System.Threading.Tasks;
 
namespace System.ServiceModel.Channels
{
    internal enum HttpAbortReason
    {
        None,
        Aborted,
        TimedOut
    }
 
    internal static class HttpChannelUtilities
    {
        internal static class StatusDescriptionStrings
        {
            internal const string HttpContentTypeMissing = "Missing Content Type";
            internal const string HttpContentTypeMismatch = "Cannot process the message because the content type '{0}' was not the expected type '{1}'.";
            internal const string HttpStatusServiceActivationException = "System.ServiceModel.ServiceActivationException";
        }
 
        internal const string HttpStatusCodeExceptionKey = "System.ServiceModel.Channels.HttpInput.HttpStatusCode";
        internal const string HttpStatusDescriptionExceptionKey = "System.ServiceModel.Channels.HttpInput.HttpStatusDescription";
        internal const string HttpRequestHeadersTypeName = "System.Net.Http.Headers.HttpRequestHeaders";
 
 
        internal const int ResponseStreamExcerptSize = 1024;
        internal const string MIMEVersionHeader = "MIME-Version";
        internal const string ContentEncodingHeader = "Content-Encoding";
 
        internal const uint CURLE_SSL_CERTPROBLEM = 58;
        internal const uint CURLE_SSL_CACERT = 60;
 
        internal const uint ERROR_INVALID_HANDLE = 6;
 
        internal const uint WININET_E_NAME_NOT_RESOLVED = 0x80072EE7;
        internal const uint WININET_E_CONNECTION_RESET = 0x80072EFF;
        internal const uint WININET_E_INCORRECT_HANDLE_STATE = 0x80072EF3;
        internal const uint ERROR_WINHTTP_SECURE_FAILURE = 0x80072f8f;
 
        public static Task<(NetworkCredential networkCredential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel)> GetCredentialAsync(
            AuthenticationSchemes authenticationScheme, SecurityTokenProviderContainer credentialProvider,
            TimeSpan timeout)
        {
            if (authenticationScheme == AuthenticationSchemes.Anonymous)
            {
                var result = ((NetworkCredential)null, TokenImpersonationLevel.None, AuthenticationLevel.None);
                return Task.FromResult(result);
            }
 
            return GetCredentialCoreAsync(authenticationScheme, credentialProvider, timeout);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static async Task<(NetworkCredential credential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel)>GetCredentialCoreAsync(
            AuthenticationSchemes authenticationScheme, SecurityTokenProviderContainer credentialProvider, TimeSpan timeout)
        {
            switch (authenticationScheme)
            {
                case AuthenticationSchemes.Basic:
                    var userNameCreds = await TransportSecurityHelpers.GetUserNameCredentialAsync(credentialProvider, timeout);
                    return (userNameCreds, TokenImpersonationLevel.Delegation, AuthenticationLevel.None);
 
                case AuthenticationSchemes.Digest:
                    return await TransportSecurityHelpers.GetSspiCredentialAsync(credentialProvider, timeout);
 
                case AuthenticationSchemes.Negotiate:
                    return await TransportSecurityHelpers.GetSspiCredentialAsync(credentialProvider, timeout);
 
                case AuthenticationSchemes.Ntlm:
                case AuthenticationSchemes.IntegratedWindowsAuthentication: // IWA could use NTLM
                    var result = await TransportSecurityHelpers.GetSspiCredentialAsync(credentialProvider, timeout);
                    if (result.authenticationLevel == AuthenticationLevel.MutualAuthRequired)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                            new InvalidOperationException(SR.CredentialDisallowsNtlm));
                    }
                    return result;
 
                default:
                    // The setter for this property should prevent this.
                    throw Fx.AssertAndThrow("GetCredential: Invalid authentication scheme");
            }
        }
 
        public static HttpResponseMessage ProcessGetResponseWebException(HttpRequestException requestException, HttpRequestMessage request, HttpAbortReason abortReason)
        {
            var inner = requestException.InnerException;
            if (inner != null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(ConvertHttpRequestException(requestException, request, abortReason));
            }
            else
            {
                // There is no inner exception so there's not enough information to be able to convert to the correct WCF exception.
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(requestException.Message, requestException));
            }
        }
 
        public static Exception ConvertHttpRequestException(HttpRequestException exception, HttpRequestMessage request, HttpAbortReason abortReason)
        {
            Contract.Assert(exception.InnerException != null, "InnerException must be set to be able to convert");
 
            uint hresult = (uint)exception.InnerException.HResult;
            var innerSocketException = exception.InnerException as SocketException;
            if (innerSocketException != null)
            {
                SocketError socketErrorCode = innerSocketException.SocketErrorCode;
                switch (socketErrorCode)
                {
                    case SocketError.TryAgain:
                    case SocketError.NoRecovery:
                    case SocketError.NoData:
                    case SocketError.HostNotFound:
                        return new EndpointNotFoundException(SR.Format(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), exception);
                    default:
                        break;
                }
            }
 
            if (exception.InnerException is AuthenticationException)
            {
                return new SecurityNegotiationException(SR.Format(SR.TrustFailure, request.RequestUri.Authority), exception);
            }
 
            switch (hresult)
            {
                // .Net Native HttpClientHandler sometimes reports an incorrect handle state when a connection is aborted, so we treat it as a connection reset error
                case WININET_E_INCORRECT_HANDLE_STATE:
                    goto case WININET_E_CONNECTION_RESET;
                case WININET_E_CONNECTION_RESET:
                    return new CommunicationException(SR.Format(SR.HttpReceiveFailure, request.RequestUri), exception);
                // Linux HttpClient returns ERROR_INVALID_HANDLE in the endpoint-not-found case, so map to EndpointNotFoundException
                case ERROR_INVALID_HANDLE:
                case WININET_E_NAME_NOT_RESOLVED:
                    return new EndpointNotFoundException(SR.Format(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), exception);
                case CURLE_SSL_CACERT:
                case CURLE_SSL_CERTPROBLEM:
                case ERROR_WINHTTP_SECURE_FAILURE:
                    return new SecurityNegotiationException(SR.Format(SR.TrustFailure, request.RequestUri.Authority), exception);
                default:
                    return new CommunicationException(exception.Message, exception);
            }
        }
 
        internal static Exception CreateUnexpectedResponseException(HttpResponseMessage response)
        {
            string statusDescription = response.ReasonPhrase;
            if (string.IsNullOrEmpty(statusDescription))
            {
                statusDescription = response.StatusCode.ToString();
            }
 
            return TraceResponseException(
                new ProtocolException(SR.Format(SR.UnexpectedHttpResponseCode,
                (int)response.StatusCode, statusDescription)));
        }
 
        internal static string GetResponseStreamExcerptString(Stream responseStream, ref int bytesToRead)
        {
            long bufferSize = bytesToRead;
 
            if (bufferSize < 0 || bufferSize > ResponseStreamExcerptSize)
            {
                bufferSize = ResponseStreamExcerptSize;
            }
 
            byte[] responseBuffer = Fx.AllocateByteArray(checked((int)bufferSize));
            bytesToRead = responseStream.Read(responseBuffer, 0, (int)bufferSize);
            responseStream.Dispose();
 
            return Encoding.UTF8.GetString(responseBuffer, 0, bytesToRead);
        }
 
        internal static Exception TraceResponseException(Exception exception)
        {
            return exception;
        }
 
        internal static ProtocolException CreateHttpProtocolException(string message, HttpStatusCode statusCode, string statusDescription)
        {
            ProtocolException exception = new ProtocolException(message);
            exception.Data.Add(HttpChannelUtilities.HttpStatusCodeExceptionKey, statusCode);
            if (statusDescription != null && statusDescription.Length > 0)
            {
                exception.Data.Add(HttpChannelUtilities.HttpStatusDescriptionExceptionKey, statusDescription);
            }
 
            return exception;
        }
    }
}