// We cannot add abstract member to a public class in order to not to break already established contract of this class. // So we add virtual method, override it everywhere internally and provide proper implementation. // Unfortunately we cannot force everyone to implement so in such case we throw NSE. protected internal virtual HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) { throw new NotSupportedException(SR.Format(SR.net_http_missing_sync_implementation, GetType(), nameof(HttpMessageHandler), nameof(Send))); }
public void OnResponseHeader(ReadOnlySpan <byte> name, ReadOnlySpan <byte> value) { if (NetEventSource.IsEnabled) { Trace($"{Encoding.ASCII.GetString(name)}: {Encoding.ASCII.GetString(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 * 1024L)); } // 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) { Trace($"Pseudo-header received in {_state} state."); } throw new HttpRequestException(SR.net_http_invalid_response_pseudo_header_in_trailer); } if (name.SequenceEqual(s_statusHeaderName)) { if (_state != StreamState.ExpectingStatus) { if (NetEventSource.IsEnabled) { Trace("Received extra status header."); } throw new HttpRequestException(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 HttpRequestException(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) { Trace("Received 100-Continue 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) { Trace($"Invalid response pseudo-header '{Encoding.ASCII.GetString(name)}'."); } throw new HttpRequestException(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) { Trace("Received header before status."); } throw new HttpRequestException(SR.net_http_invalid_response); } if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor)) { // Invalid header name throw new HttpRequestException(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); } } } }
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); } }
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)) { return(Task.FromException <HttpResponseMessage>( new HttpRequestException(SR.net_http_client_execution_error, new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content)))); } if (_useCookies && _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)); } // 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, _agent, request, cancellationToken); try { EventSourceTrace("{0}", request, easy: easy); _agent.Queue(new MultiAgent.IncomingRequest { Easy = easy, Type = MultiAgent.IncomingRequestType.New }); } catch (Exception exc) { easy.CleanupAndFailRequest(exc); } return(easy.Task); }
public override async ValueTask <int> ReadAsync(Memory <byte> buffer, CancellationToken cancellationToken) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); if (_connection == null) { // Response body fully consumed return(0); } Debug.Assert(_contentBytesRemaining > 0); if ((ulong)buffer.Length > _contentBytesRemaining) { buffer = buffer.Slice(0, (int)_contentBytesRemaining); } ValueTask <int> readTask = _connection.ReadAsync(buffer); int bytesRead; if (readTask.IsCompletedSuccessfully) { bytesRead = readTask.Result; } else { CancellationTokenRegistration ctr = _connection.RegisterCancellation(cancellationToken); try { bytesRead = await readTask.ConfigureAwait(false); } catch (Exception exc) when(CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationToken)) { throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken); } finally { ctr.Dispose(); } } if (bytesRead == 0 && buffer.Length != 0) { // A cancellation request may have caused the EOF. CancellationHelper.ThrowIfCancellationRequested(cancellationToken); // Unexpected end of response stream. throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _contentBytesRemaining)); } Debug.Assert((ulong)bytesRead <= _contentBytesRemaining); _contentBytesRemaining -= (ulong)bytesRead; if (_contentBytesRemaining == 0) { // End of response body _connection.CompleteResponse(); _connection = null; } return(bytesRead); }
public async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { Debug.Assert(async); // Allocate an active request QuicStream? quicStream = null; Http3RequestStream?requestStream = null; ValueTask waitTask = default; try { while (true) { lock (SyncObj) { if (_connection == null) { break; } if (_connection.GetRemoteAvailableBidirectionalStreamCount() > 0) { quicStream = _connection.OpenBidirectionalStream(); requestStream = new Http3RequestStream(request, this, quicStream); _activeRequests.Add(quicStream, requestStream); break; } waitTask = _connection.WaitForAvailableBidirectionalStreamsAsync(cancellationToken); } // Wait for an available stream (based on QUIC MAX_STREAMS) if there isn't one available yet. await waitTask.ConfigureAwait(false); } if (quicStream == null) { throw new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnConnectionFailure); } requestStream !.StreamId = quicStream.StreamId; bool goAway; lock (SyncObj) { goAway = _lastProcessedStreamId != -1 && requestStream.StreamId > _lastProcessedStreamId; } if (goAway) { throw new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnConnectionFailure); } Task <HttpResponseMessage> responseTask = requestStream.SendAsync(cancellationToken); // null out requestStream to avoid disposing in finally block. It is now in charge of disposing itself. requestStream = null; return(await responseTask.ConfigureAwait(false)); } catch (QuicConnectionAbortedException ex) { // This will happen if we aborted _connection somewhere. Abort(ex); throw new HttpRequestException(SR.Format(SR.net_http_http3_connection_error, ex.ErrorCode), ex, RequestRetryType.RetryOnConnectionFailure); } finally { requestStream?.Dispose(); } }
private async ValueTask <int> ReadAsyncCore(Memory <byte> buffer, CancellationToken cancellationToken) { // Should only be called if ReadChunksFromConnectionBuffer returned 0. Debug.Assert(_connection != null); Debug.Assert(buffer.Length > 0); CancellationTokenRegistration ctr = _connection.RegisterCancellation(cancellationToken); try { while (true) { if (_connection == null) { // Fully consumed the response in ReadChunksFromConnectionBuffer. return(0); } if (_state == ParsingState.ExpectChunkData && buffer.Length >= _connection.ReadBufferSize && _chunkBytesRemaining >= (ulong)_connection.ReadBufferSize) { // As an optimization, we skip going through the connection's read buffer if both // the remaining chunk data and the buffer are both at least as large // as the connection buffer. That avoids an unnecessary copy while still reading // the maximum amount we'd otherwise read at a time. Debug.Assert(_connection.RemainingBuffer.Length == 0); int bytesRead = await _connection.ReadAsync(buffer.Slice(0, (int)Math.Min((ulong)buffer.Length, _chunkBytesRemaining))).ConfigureAwait(false); if (bytesRead == 0) { throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _chunkBytesRemaining)); } _chunkBytesRemaining -= (ulong)bytesRead; if (_chunkBytesRemaining == 0) { _state = ParsingState.ExpectChunkTerminator; } return(bytesRead); } // We're only here if we need more data to make forward progress. await _connection.FillAsync().ConfigureAwait(false); // Now that we have more, see if we can get any response data, and if // we can we're done. int bytesCopied = ReadChunksFromConnectionBuffer(buffer.Span, ctr); if (bytesCopied > 0) { return(bytesCopied); } } } catch (Exception exc) when(CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationToken)) { throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken); } finally { ctr.Dispose(); } }