Beispiel #1
0
        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);
        }
Beispiel #2
0
            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);
                }
            }
Beispiel #3
0
            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);
                        }
                    }
                }
            }