| 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; } } }