protected internal override Task <HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest); } if (request.RequestUri.Scheme == UriSchemeHttps) { if (!s_supportsSSL) { throw new PlatformNotSupportedException(SR.Format(SR.net_http_unix_https_support_unavailable_libcurl, CurlVersionDescription)); } } else { Debug.Assert(request.RequestUri.Scheme == UriSchemeHttp, "HttpClient expected to validate scheme as http or https."); } if (request.Headers.TransferEncodingChunked.GetValueOrDefault() && (request.Content == null)) { throw new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content); } if (_useCookie && _cookieContainer == null) { throw new InvalidOperationException(SR.net_http_invalid_cookiecontainer); } CheckDisposed(); SetOperationStarted(); // Do an initial cancellation check to avoid initiating the async operation if // cancellation has already been requested. After this, we'll rely on CancellationToken.Register // to notify us of cancellation requests and shut down the operation if possible. if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <HttpResponseMessage>(cancellationToken)); } EventSourceTrace("{0}", request); Guid loggingRequestId = s_diagnosticListener.LogHttpRequest(request); // Create the easy request. This associates the easy request with this handler and configures // it based on the settings configured for the handler. var easy = new EasyRequest(this, request, cancellationToken); try { _agent.Queue(new MultiAgent.IncomingRequest { Easy = easy, Type = MultiAgent.IncomingRequestType.New }); } catch (Exception exc) { easy.FailRequestAndCleanup(exc); } s_diagnosticListener.LogHttpResponse(easy.Task, loggingRequestId); return(easy.Task); }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; ClientCertificateProvider certProvider = clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs null; // manual without certs IntPtr userPointer = IntPtr.Zero; if (certProvider != null) { // The client cert provider needs to be passed through to the callback, and thus // we create a GCHandle to keep it rooted. This handle needs to be cleaned up // when the request has completed, and a simple and pay-for-play way to do that // is by cleaning it up in a continuation off of the request. userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } // Register the callback with libcurl. We need to register even if there's no user-provided // server callback and even if there are no client certificates, because we support verifying // server certificates against more than those known to OpenSSL. if (CurlSslVersionDescription.IndexOf("openssl/1.0", StringComparison.OrdinalIgnoreCase) != -1) { CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); switch (answer) { case CURLcode.CURLE_OK: // We successfully registered. If we'll be invoking a user-provided callback to verify the server // certificate as part of that, disable libcurl's verification of the host name. The user's callback // needs to be given the opportunity to examine the cert, and our logic will determine whether // the host name matches and will inform the callback of that. if (easy._handler.ServerCertificateValidationCallback != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the peer cert's hostname // We don't change the SSL_VERIFYPEER setting, as setting it to 0 will cause // SSL and libcurl to ignore the result of the server callback. } // The allowed SSL protocols will be set in the configuration callback. break; case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later // It's ok if we failed to register the callback if all of the defaults are in play // with relation to handling of certificates. But if that's not the case, failing to // register the callback will result in those options not being factored in, which is // a significant enough error that we need to fail. EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported: {0}", answer, easy: easy); if (certProvider != null || easy._handler.ServerCertificateValidationCallback != null || easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException( SR.Format(SR.net_http_unix_invalid_certcallback_option, CurlVersionDescription, CurlSslVersionDescription)); } // Since there won't be a callback to configure the allowed SSL protocols, configure them here. SetSslVersion(easy); break; default: ThrowIfCURLEError(answer); break; } } else { // For newer versions of openssl throw PNSE, if default not used. if (certProvider != null) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_clientcerts_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } if (easy._handler.ServerCertificateValidationCallback != null) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_callback_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } if (easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_revocation_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } // In case of defaults configure the allowed SSL protocols. SetSslVersion(easy); } }
public void OnResponseHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) { Debug.Assert(name != null && name.Length > 0); _headerBudgetRemaining -= name.Length + value.Length; if (_headerBudgetRemaining < 0) { throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection._pool.Settings._maxResponseHeadersLength)); } // TODO: ISSUE 31309: Optimize HPACK static table decoding lock (SyncObject) { if (_state == StreamState.Aborted) { // We could have aborted while processing the header block. return; } if (name[0] == (byte)':') { if (_state != StreamState.ExpectingHeaders && _state != StreamState.ExpectingStatus) { // Pseudo-headers are allowed only in header block if (NetEventSource.IsEnabled) _connection.Trace($"Pseudo-header in {_state} state."); throw new Http2ProtocolException(SR.net_http_invalid_response_pseudo_header_in_trailer); } if (name.SequenceEqual(s_statusHeaderName)) { if (_state != StreamState.ExpectingStatus) { if (NetEventSource.IsEnabled) _connection.Trace("Received duplicate status headers."); throw new Http2ProtocolException(SR.Format(SR.net_http_invalid_response_status_code, "duplicate status")); } byte status1, status2, status3; if (value.Length != 3 || !IsDigit(status1 = value[0]) || !IsDigit(status2 = value[1]) || !IsDigit(status3 = value[2])) { throw new Http2ProtocolException(SR.Format(SR.net_http_invalid_response_status_code, Encoding.ASCII.GetString(value))); } int statusValue = (100 * (status1 - '0') + 10 * (status2 - '0') + (status3 - '0')); _response = new HttpResponseMessage() { Version = HttpVersion.Version20, RequestMessage = _request, Content = new HttpConnectionResponseContent(), StatusCode = (HttpStatusCode)statusValue }; TaskCompletionSource<bool> shouldSendRequestBodyWaiter = _shouldSendRequestBodyWaiter; if (statusValue < 200) { if (_response.StatusCode == HttpStatusCode.Continue && shouldSendRequestBodyWaiter != null) { if (NetEventSource.IsEnabled) _connection.Trace("Received 100Continue status."); shouldSendRequestBodyWaiter.TrySetResult(true); _shouldSendRequestBodyWaiter = null; } // We do not process headers from 1xx responses. _state = StreamState.ExpectingIgnoredHeaders; } else { _state = StreamState.ExpectingHeaders; // If we tried 100-Continue and got rejected signal that we should not send request body. _shouldSendRequestBody = (int)Response.StatusCode < 300; shouldSendRequestBodyWaiter?.TrySetResult(_shouldSendRequestBody); } } else { if (NetEventSource.IsEnabled) _connection.Trace($"Invalid response pseudo-header '{Encoding.ASCII.GetString(name)}'."); throw new Http2ProtocolException(SR.net_http_invalid_response); } } else { if (_state == StreamState.ExpectingIgnoredHeaders) { // for 1xx response we ignore all headers. return; } if (_state != StreamState.ExpectingHeaders && _state != StreamState.ExpectingTrailingHeaders) { if (NetEventSource.IsEnabled) _connection.Trace("Received header before status."); throw new Http2ProtocolException(SR.net_http_invalid_response); } if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor)) { // Invalid header name throw new Http2ProtocolException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name))); } string headerValue = descriptor.GetHeaderValue(value); // Note we ignore the return value from TryAddWithoutValidation; // if the header can't be added, we silently drop it. if (_state == StreamState.ExpectingTrailingHeaders) { _response.TrailingHeaders.TryAddWithoutValidation(descriptor.HeaderType == HttpHeaderType.Request ? descriptor.AsCustomHeader() : descriptor, headerValue); } else if (descriptor.HeaderType == HttpHeaderType.Content) { _response.Content.Headers.TryAddWithoutValidation(descriptor, headerValue); } else { _response.Headers.TryAddWithoutValidation(descriptor.HeaderType == HttpHeaderType.Request ? descriptor.AsCustomHeader() : descriptor, headerValue); } } } }