< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 2071
Coverable lines: 2071
Total lines: 3625
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 804
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .ctor(...)100%110%
File 1: Read(...)0%28280%
File 1: ReadAsync(...)0%12120%
File 1: ReadAsyncCore()0%20200%
File 1: CopyToAsync(...)0%440%
File 1: CopyToAsyncCore()0%660%
File 1: PeekChunkFromConnectionBuffer()100%110%
File 1: ReadChunksFromConnectionBuffer(...)0%660%
File 1: ReadChunkFromConnectionBuffer(...)0%34340%
File 1: ValidateChunkExtension(...)0%880%
File 1: DrainAsync()0%16160%
File 1: Fill()0%220%
File 1: FillAsync()0%220%
File 2: .cctor()100%110%
File 2: .ctor(...)100%110%
File 2: Write(...)0%220%
File 2: WriteAsync(...)0%220%
File 2: WriteChunkAsync()100%110%
File 2: FinishAsync(...)0%220%
File 3: .ctor(...)100%110%
File 3: Read(...)0%660%
File 3: ReadAsync()0%880%
File 3: CopyToAsync(...)0%660%
File 3: CompleteCopyToAsync()100%110%
File 3: Finish(...)100%110%
File 4: .ctor(...)100%110%
File 4: Read(...)0%10100%
File 4: ReadAsync()0%12120%
File 4: CopyToAsync(...)0%660%
File 4: CompleteCopyToAsync()100%110%
File 4: Finish()100%110%
File 4: ReadFromConnectionBuffer(...)0%220%
File 4: DrainAsync()0%12120%
File 5: .ctor(...)100%110%
File 5: Write(...)0%220%
File 5: WriteAsync(...)0%220%
File 5: FinishAsync(...)0%220%
File 6: .cctor()100%110%
File 6: .ctor(...)0%220%
File 6: Finalize()100%110%
File 6: Dispose()100%110%
File 6: Dispose(...)0%880%
File 6: PrepareForReuse(...)0%16160%
File 6: TryOwnScavengingTaskCompletion()0%220%
File 6: TryReturnScavengingTaskCompletionOwnership()0%440%
File 6: CheckUsabilityOnScavenge()0%440%
File 6: ReadAheadWithZeroByteReadAsync()0%660%
File 6: TransitionToCompletedAndTryOwnCompletion()0%220%
File 6: CheckKeepAliveTimeoutExceeded()0%220%
File 6: ConsumeFromRemainingBuffer(...)100%110%
File 6: WriteHeaders(...)0%32320%
File 6: WriteHost(System.Uri)0%440%
File 6: WriteHeaderCollection(...)0%18180%
File 6: WriteCRLF()100%110%
File 6: WriteBytes(...)100%110%
File 6: WriteAsciiString(...)100%110%
File 6: WriteString(...)0%440%
File 6: ThrowForInvalidCharEncoding()100%110%
File 6: SendAsync()0%1181180%
File 6: MapSendException(...)0%10100%
File 6: CreateRequestContentStream(...)0%440%
File 6: RegisterCancellation(...)0%220%
File 6: SendRequestContentAsync()0%880%
File 6: SendRequestContentWithExpect100ContinueAsync()0%660%
File 6: ParseStatusLine(...)0%880%
File 6: ParseStatusLineCore(...)0%26260%
File 6: ParseHeaders(...)0%440%
File 6: ParseHeadersCore(...)0%26260%
File 6: ThrowForInvalidHeaderLine(System.ReadOnlySpan`1<System.Byte>,System.Int32)100%110%
File 6: AddResponseHeader(...)0%34340%
File 6: ThrowForEmptyHeaderName()100%110%
File 6: ThrowForInvalidHeaderName(System.ReadOnlySpan`1<System.Byte>)100%110%
File 6: ThrowExceededAllowedReadLineBytes()100%110%
File 6: ProcessKeepAliveHeader(...)0%18180%
File 6: WriteToBuffer(...)100%110%
File 6: Write(...)0%660%
File 6: WriteAsync(...)0%880%
File 6: AwaitFlushAndWriteAsync()0%220%
File 6: WriteWithoutBuffering(...)0%440%
File 6: WriteWithoutBufferingAsync(...)0%440%
File 6: FlushThenWriteWithoutBufferingAsync()100%110%
File 6: WriteHexInt32Async(...)0%440%
File 6: Flush()0%220%
File 6: FlushAsync(...)0%220%
File 6: WriteToStream(...)0%220%
File 6: WriteToStreamAsync(...)0%440%
File 6: TryReadNextChunkedLine(...)0%10100%
File 6: InitialFillAsync()0%440%
File 6: FillAsync()0%660%
File 6: FillForHeadersAsync(...)0%220%
File 6: ReadUntilEndOfHeaderAsync()0%12120%
File 6: TryFindEndOfLine(System.ReadOnlySpan`1<System.Byte>,System.Int32&)0%880%
File 6: ReadFromBuffer(...)100%110%
File 6: Read(...)0%440%
File 6: ReadAsync(...)0%440%
File 6: ReadAndLogBytesReadAsync()0%220%
File 6: ReadBuffered(...)0%660%
File 6: ReadBufferedAsync(...)0%440%
File 6: ReadBufferedAsyncCore()0%440%
File 6: CopyFromBufferAsync(...)0%440%
File 6: CopyToUntilEofAsync(...)0%440%
File 6: CopyToUntilEofWithExistingBufferedDataAsync()0%220%
File 6: CopyToContentLengthAsync()0%20200%
File 6: Acquire()100%110%
File 6: Release()0%220%
File 6: DetachFromPool()100%110%
File 6: CompleteResponse()0%880%
File 6: DrainResponseAsync()0%12120%
File 6: ReturnConnectionToPool()0%440%
File 6: ToString()100%110%
File 6: Trace(...)0%440%
File 7: .ctor(...)100%110%
File 7: Write(...)100%110%
File 7: WriteAsync(...)100%110%
File 7: DrainAsync(...)100%110%
File 7: Dispose(...)0%660%
File 7: DrainOnDisposeAsync()0%660%
File 8: .ctor(...)100%110%
File 8: Flush()0%220%
File 8: FlushAsync(...)0%220%
File 8: Read(...)100%110%
File 8: ReadAsync(...)100%110%
File 8: CopyToAsync(...)100%110%
File 9: .ctor(...)0%220%
File 9: Read(...)0%660%
File 9: ReadAsync()0%880%
File 9: CopyToAsync(...)0%660%
File 9: CompleteCopyToAsync()100%110%
File 9: Finish(...)100%110%
File 9: Write(...)0%440%
File 9: WriteAsync(...)0%880%
File 9: Flush()0%220%
File 9: FlushAsync(...)0%660%
File 9: WaitWithConnectionCancellationAsync()100%110%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ChunkedEncodingReadStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Text;
 5using System.Diagnostics;
 6using System.IO;
 7using System.Text;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace System.Net.Http
 12{
 13    internal sealed partial class HttpConnection
 14    {
 15        private sealed class ChunkedEncodingReadStream : HttpContentReadStream
 16        {
 17            /// <summary>The number of bytes remaining in the chunk.</summary>
 18            private ulong _chunkBytesRemaining;
 19            /// <summary>The current state of the parsing state machine for the chunked response.</summary>
 020            private ParsingState _state = ParsingState.ExpectChunkHeader;
 21            private readonly HttpResponseMessage _response;
 22
 023            public ChunkedEncodingReadStream(HttpConnection connection, HttpResponseMessage response) : base(connection)
 024            {
 025                Debug.Assert(response != null, "The HttpResponseMessage cannot be null.");
 026                _response = response;
 027            }
 28
 29            public override int Read(Span<byte> buffer)
 030            {
 031                if (_connection == null)
 032                {
 33                    // Response body fully consumed
 034                    return 0;
 35                }
 36
 037                if (buffer.Length == 0)
 038                {
 039                    if (PeekChunkFromConnectionBuffer())
 040                    {
 041                        return 0;
 42                    }
 043                }
 44                else
 045                {
 46                    // Try to consume from data we already have in the buffer.
 047                    int bytesRead = ReadChunksFromConnectionBuffer(buffer, cancellationRegistration: default);
 048                    if (bytesRead > 0)
 049                    {
 050                        return bytesRead;
 51                    }
 052                }
 53
 54                // Nothing available to consume.  Fall back to I/O.
 055                while (true)
 056                {
 057                    if (_connection == null)
 058                    {
 59                        // Fully consumed the response in ReadChunksFromConnectionBuffer.
 060                        return 0;
 61                    }
 62
 063                    if (_state == ParsingState.ExpectChunkData &&
 064                        buffer.Length >= _connection.ReadBufferSize &&
 065                        _chunkBytesRemaining >= (ulong)_connection.ReadBufferSize)
 066                    {
 67                        // As an optimization, we skip going through the connection's read buffer if both
 68                        // the remaining chunk data and the buffer are both at least as large
 69                        // as the connection buffer.  That avoids an unnecessary copy while still reading
 70                        // the maximum amount we'd otherwise read at a time.
 071                        Debug.Assert(_connection.RemainingBuffer.Length == 0);
 072                        Debug.Assert(buffer.Length != 0);
 073                        int bytesRead = _connection.Read(buffer.Slice(0, (int)Math.Min((ulong)buffer.Length, _chunkBytes
 074                        if (bytesRead == 0)
 075                        {
 076                            throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_resp
 77                        }
 078                        _chunkBytesRemaining -= (ulong)bytesRead;
 079                        if (_chunkBytesRemaining == 0)
 080                        {
 081                            _state = ParsingState.ExpectChunkTerminator;
 082                        }
 083                        return bytesRead;
 84                    }
 85
 086                    if (buffer.Length == 0)
 087                    {
 88                        // User requested a zero-byte read, and we have no data available in the buffer for processing.
 89                        // This zero-byte read indicates their desire to trade off the extra cost of a zero-byte read
 90                        // for reduced memory consumption when data is not immediately available.
 91                        // So, we will issue our own zero-byte read against the underlying stream to allow it to make us
 92                        // optimizations, such as deferring buffer allocation until data is actually available.
 093                        _connection.Read(buffer);
 094                    }
 95
 96                    // We're only here if we need more data to make forward progress.
 097                    Fill();
 98
 99                    // Now that we have more, see if we can get any response data, and if
 100                    // we can we're done.
 0101                    if (buffer.Length == 0)
 0102                    {
 0103                        if (PeekChunkFromConnectionBuffer())
 0104                        {
 0105                            return 0;
 106                        }
 0107                    }
 108                    else
 0109                    {
 0110                        int bytesCopied = ReadChunksFromConnectionBuffer(buffer, cancellationRegistration: default);
 0111                        if (bytesCopied > 0)
 0112                        {
 0113                            return bytesCopied;
 114                        }
 0115                    }
 0116                }
 0117            }
 118
 119            public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 0120            {
 0121                if (cancellationToken.IsCancellationRequested)
 0122                {
 123                    // Cancellation requested.
 0124                    return ValueTask.FromCanceled<int>(cancellationToken);
 125                }
 126
 0127                if (_connection == null)
 0128                {
 129                    // Response body fully consumed
 0130                    return new ValueTask<int>(0);
 131                }
 132
 0133                if (buffer.Length == 0)
 0134                {
 0135                    if (PeekChunkFromConnectionBuffer())
 0136                    {
 0137                        return new ValueTask<int>(0);
 138                    }
 0139                }
 140                else
 0141                {
 142                    // Try to consume from data we already have in the buffer.
 0143                    int bytesRead = ReadChunksFromConnectionBuffer(buffer.Span, cancellationRegistration: default);
 0144                    if (bytesRead > 0)
 0145                    {
 0146                        return new ValueTask<int>(bytesRead);
 147                    }
 0148                }
 149
 150                // We may have just consumed the remainder of the response (with no actual data
 151                // available), so check again.
 0152                if (_connection == null)
 0153                {
 0154                    Debug.Assert(_state == ParsingState.Done);
 0155                    return new ValueTask<int>(0);
 156                }
 157
 158                // Nothing available to consume.  Fall back to I/O.
 0159                return ReadAsyncCore(buffer, cancellationToken);
 0160            }
 161
 162            private async ValueTask<int> ReadAsyncCore(Memory<byte> buffer, CancellationToken cancellationToken)
 0163            {
 164                // Should only be called if ReadChunksFromConnectionBuffer returned 0.
 165
 0166                Debug.Assert(_connection != null);
 167
 0168                CancellationTokenRegistration ctr = _connection.RegisterCancellation(cancellationToken);
 169                try
 0170                {
 0171                    while (true)
 0172                    {
 0173                        if (_connection == null)
 0174                        {
 175                            // Fully consumed the response in ReadChunksFromConnectionBuffer.
 0176                            return 0;
 177                        }
 178
 0179                        if (_state == ParsingState.ExpectChunkData &&
 0180                            buffer.Length >= _connection.ReadBufferSize &&
 0181                            _chunkBytesRemaining >= (ulong)_connection.ReadBufferSize)
 0182                        {
 183                            // As an optimization, we skip going through the connection's read buffer if both
 184                            // the remaining chunk data and the buffer are both at least as large
 185                            // as the connection buffer.  That avoids an unnecessary copy while still reading
 186                            // the maximum amount we'd otherwise read at a time.
 0187                            Debug.Assert(_connection.RemainingBuffer.Length == 0);
 0188                            Debug.Assert(buffer.Length != 0);
 0189                            int bytesRead = await _connection.ReadAsync(buffer.Slice(0, (int)Math.Min((ulong)buffer.Leng
 0190                            if (bytesRead == 0)
 0191                            {
 0192                                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_
 193                            }
 0194                            _chunkBytesRemaining -= (ulong)bytesRead;
 0195                            if (_chunkBytesRemaining == 0)
 0196                            {
 0197                                _state = ParsingState.ExpectChunkTerminator;
 0198                            }
 0199                            return bytesRead;
 200                        }
 201
 0202                        if (buffer.Length == 0)
 0203                        {
 204                            // User requested a zero-byte read, and we have no data available in the buffer for processi
 205                            // This zero-byte read indicates their desire to trade off the extra cost of a zero-byte rea
 206                            // for reduced memory consumption when data is not immediately available.
 207                            // So, we will issue our own zero-byte read against the underlying stream to allow it to mak
 208                            // optimizations, such as deferring buffer allocation until data is actually available.
 0209                            await _connection.ReadAsync(buffer).ConfigureAwait(false);
 0210                        }
 211
 212                        // We're only here if we need more data to make forward progress.
 0213                        await FillAsync().ConfigureAwait(false);
 214
 215                        // Now that we have more, see if we can get any response data, and if
 216                        // we can we're done.
 0217                        if (buffer.Length == 0)
 0218                        {
 0219                            if (PeekChunkFromConnectionBuffer())
 0220                            {
 0221                                return 0;
 222                            }
 0223                        }
 224                        else
 0225                        {
 0226                            int bytesCopied = ReadChunksFromConnectionBuffer(buffer.Span, ctr);
 0227                            if (bytesCopied > 0)
 0228                            {
 0229                                return bytesCopied;
 230                            }
 0231                        }
 0232                    }
 233                }
 0234                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0235                {
 0236                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 237                }
 238                finally
 0239                {
 0240                    ctr.Dispose();
 0241                }
 0242            }
 243
 244            public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
 0245            {
 0246                ValidateCopyToArguments(destination, bufferSize);
 247
 0248                return
 0249                    cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
 0250                    _connection == null ? Task.CompletedTask :
 0251                    CopyToAsyncCore(destination, cancellationToken);
 0252            }
 253
 254            private async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken)
 0255            {
 0256                CancellationTokenRegistration ctr = _connection!.RegisterCancellation(cancellationToken);
 257                try
 0258                {
 0259                    while (true)
 0260                    {
 0261                        while (true)
 0262                        {
 0263                            if (ReadChunkFromConnectionBuffer(int.MaxValue, ctr) is not ReadOnlyMemory<byte> bytesRead |
 0264                            {
 0265                                break;
 266                            }
 0267                            await destination.WriteAsync(bytesRead, cancellationToken).ConfigureAwait(false);
 0268                        }
 269
 0270                        if (_connection == null)
 0271                        {
 272                            // Fully consumed the response.
 0273                            return;
 274                        }
 275
 0276                        await FillAsync().ConfigureAwait(false);
 0277                    }
 278                }
 0279                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0280                {
 0281                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 282                }
 283                finally
 0284                {
 0285                    ctr.Dispose();
 0286                }
 0287            }
 288
 289            private bool PeekChunkFromConnectionBuffer()
 0290            {
 0291                return ReadChunkFromConnectionBuffer(maxBytesToRead: 0, cancellationRegistration: default).HasValue;
 0292            }
 293
 294            private int ReadChunksFromConnectionBuffer(Span<byte> buffer, CancellationTokenRegistration cancellationRegi
 0295            {
 0296                Debug.Assert(buffer.Length > 0);
 0297                int totalBytesRead = 0;
 0298                while (buffer.Length > 0)
 0299                {
 0300                    if (ReadChunkFromConnectionBuffer(buffer.Length, cancellationRegistration) is not ReadOnlyMemory<byt
 0301                    {
 0302                        break;
 303                    }
 304
 0305                    Debug.Assert(bytesRead.Length <= buffer.Length);
 0306                    totalBytesRead += bytesRead.Length;
 0307                    bytesRead.Span.CopyTo(buffer);
 0308                    buffer = buffer.Slice(bytesRead.Length);
 0309                }
 0310                return totalBytesRead;
 0311            }
 312
 313            private ReadOnlyMemory<byte>? ReadChunkFromConnectionBuffer(int maxBytesToRead, CancellationTokenRegistratio
 0314            {
 0315                Debug.Assert(_connection != null);
 316
 317                try
 0318                {
 319                    ReadOnlySpan<byte> currentLine;
 0320                    switch (_state)
 321                    {
 322                        case ParsingState.ExpectChunkHeader:
 0323                            Debug.Assert(_chunkBytesRemaining == 0, $"Expected {nameof(_chunkBytesRemaining)} == 0, got 
 324
 325                            // Read the chunk header line.
 0326                            if (!_connection.TryReadNextChunkedLine(out currentLine))
 0327                            {
 328                                // Could not get a whole line, so we can't parse the chunk header.
 0329                                return default;
 330                            }
 331
 332                            // Parse the hex value from it.
 0333                            if (!Utf8Parser.TryParse(currentLine, out ulong chunkSize, out int bytesConsumed, 'X'))
 0334                            {
 0335                                throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invali
 336                            }
 0337                            _chunkBytesRemaining = chunkSize;
 338
 339                            // If there's a chunk extension after the chunk size, validate it.
 0340                            if (bytesConsumed != currentLine.Length)
 0341                            {
 0342                                ValidateChunkExtension(currentLine.Slice(bytesConsumed));
 0343                            }
 344
 345                            // Proceed to handle the chunk.  If there's data in it, go read it.
 346                            // Otherwise, finish handling the response.
 0347                            if (chunkSize > 0)
 0348                            {
 0349                                _state = ParsingState.ExpectChunkData;
 0350                                goto case ParsingState.ExpectChunkData;
 351                            }
 352                            else
 0353                            {
 0354                                _state = ParsingState.ConsumeTrailers;
 0355                                goto case ParsingState.ConsumeTrailers;
 356                            }
 357
 358                        case ParsingState.ExpectChunkData:
 0359                            Debug.Assert(_chunkBytesRemaining > 0);
 360
 0361                            ReadOnlyMemory<byte> connectionBuffer = _connection.RemainingBuffer;
 0362                            if (connectionBuffer.Length == 0)
 0363                            {
 0364                                return default;
 365                            }
 366
 0367                            int bytesToConsume = Math.Min(maxBytesToRead, (int)Math.Min((ulong)connectionBuffer.Length, 
 0368                            Debug.Assert(bytesToConsume > 0 || maxBytesToRead == 0);
 369
 0370                            _connection.ConsumeFromRemainingBuffer(bytesToConsume);
 0371                            _chunkBytesRemaining -= (ulong)bytesToConsume;
 0372                            if (_chunkBytesRemaining == 0)
 0373                            {
 0374                                _state = ParsingState.ExpectChunkTerminator;
 0375                            }
 376
 0377                            return connectionBuffer.Slice(0, bytesToConsume);
 378
 379                        case ParsingState.ExpectChunkTerminator:
 0380                            Debug.Assert(_chunkBytesRemaining == 0, $"Expected {nameof(_chunkBytesRemaining)} == 0, got 
 381
 0382                            if (!_connection.TryReadNextChunkedLine(out currentLine))
 0383                            {
 0384                                return default;
 385                            }
 386
 0387                            if (currentLine.Length != 0)
 0388                            {
 0389                                throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invali
 390                            }
 391
 0392                            _state = ParsingState.ExpectChunkHeader;
 0393                            goto case ParsingState.ExpectChunkHeader;
 394
 395                        case ParsingState.ConsumeTrailers:
 0396                            Debug.Assert(_chunkBytesRemaining == 0, $"Expected {nameof(_chunkBytesRemaining)} == 0, got 
 397
 398                            // Consume the receive buffer. If the stream is disposed, pass a null response to avoid
 399                            // processing headers for a connection returned to the pool.
 0400                            if (_connection.ParseHeaders(IsDisposed ? null : _response, isFromTrailer: true))
 0401                            {
 402                                // Dispose of the registration and then check whether cancellation has been
 403                                // requested. This is necessary to make deterministic a race condition between
 404                                // cancellation being requested and unregistering from the token.  Otherwise,
 405                                // it's possible cancellation could be requested just before we unregister and
 406                                // we then return a connection to the pool that has been or will be disposed
 407                                // (e.g. if a timer is used and has already queued its callback but the
 408                                // callback hasn't yet run).
 0409                                cancellationRegistration.Dispose();
 0410                                CancellationHelper.ThrowIfCancellationRequested(cancellationRegistration.Token);
 411
 0412                                _state = ParsingState.Done;
 0413                                _connection.CompleteResponse();
 0414                                _connection = null;
 0415                            }
 416
 0417                            return default;
 418
 419                        default:
 420                        case ParsingState.Done: // shouldn't be called once we're done
 0421                            Debug.Fail($"Unexpected state: {_state}");
 422                            if (NetEventSource.Log.IsEnabled())
 423                            {
 424                                NetEventSource.Error(this, $"Unexpected state: {_state}");
 425                            }
 426
 427                            return default;
 428                    }
 429                }
 0430                catch (Exception)
 0431                {
 432                    // Ensure we don't try to read from the connection again (in particular, for draining)
 0433                    _connection!.Dispose();
 0434                    _connection = null;
 0435                    throw;
 436                }
 0437            }
 438
 439            private static void ValidateChunkExtension(ReadOnlySpan<byte> lineAfterChunkSize)
 0440            {
 441                // Until we see the ';' denoting the extension, the line after the chunk size
 442                // must contain only tabs and spaces.  After the ';', anything goes.
 0443                for (int i = 0; i < lineAfterChunkSize.Length; i++)
 0444                {
 0445                    byte c = lineAfterChunkSize[i];
 0446                    if (c == ';')
 0447                    {
 0448                        break;
 449                    }
 0450                    else if (c != ' ' && c != '\t') // not called out in the RFC, but WinHTTP allows it
 0451                    {
 0452                        throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_respon
 453                    }
 0454                }
 0455            }
 456
 457            private enum ParsingState : byte
 458            {
 459                ExpectChunkHeader,
 460                ExpectChunkData,
 461                ExpectChunkTerminator,
 462                ConsumeTrailers,
 463                Done
 464            }
 465
 0466            public override bool NeedsDrain => CanReadFromConnection;
 467
 468            public override async ValueTask<bool> DrainAsync(int maxDrainBytes)
 0469            {
 0470                Debug.Assert(_connection != null);
 471
 0472                CancellationTokenSource? cts = null;
 0473                CancellationTokenRegistration ctr = default;
 474                try
 0475                {
 0476                    int drainedBytes = 0;
 0477                    while (true)
 0478                    {
 0479                        drainedBytes += _connection.RemainingBuffer.Length;
 0480                        while (true)
 0481                        {
 0482                            if (ReadChunkFromConnectionBuffer(int.MaxValue, ctr) is not ReadOnlyMemory<byte> bytesRead |
 0483                            {
 0484                                break;
 485                            }
 0486                        }
 487
 488                        // When ReadChunkFromConnectionBuffer reads the final chunk, it will clear out _connection
 489                        // and return the connection to the pool.
 0490                        if (_connection == null)
 0491                        {
 0492                            return true;
 493                        }
 494
 0495                        if (drainedBytes >= maxDrainBytes)
 0496                        {
 0497                            return false;
 498                        }
 499
 0500                        if (cts == null) // only create the drain timer if we have to go async
 0501                        {
 0502                            TimeSpan drainTime = _connection._pool.Settings._maxResponseDrainTime;
 503
 0504                            if (drainTime == TimeSpan.Zero)
 0505                            {
 0506                                return false;
 507                            }
 508
 0509                            if (drainTime != Timeout.InfiniteTimeSpan)
 0510                            {
 0511                                cts = new CancellationTokenSource((int)drainTime.TotalMilliseconds);
 0512                                ctr = cts.Token.Register(static s => ((HttpConnection)s!).Dispose(), _connection);
 0513                            }
 0514                        }
 515
 0516                        await FillAsync().ConfigureAwait(false);
 0517                    }
 518                }
 519                finally
 0520                {
 0521                    ctr.Dispose();
 0522                    cts?.Dispose();
 0523                }
 0524            }
 525
 526            private void Fill()
 0527            {
 0528                Debug.Assert(_connection is not null);
 0529                ValueTask fillTask = _state == ParsingState.ConsumeTrailers
 0530                    ? _connection.FillForHeadersAsync(async: false)
 0531                    : _connection.FillAsync(async: false);
 0532                Debug.Assert(fillTask.IsCompleted);
 0533                fillTask.GetAwaiter().GetResult();
 0534            }
 535
 536            private ValueTask FillAsync()
 0537            {
 0538                Debug.Assert(_connection is not null);
 0539                return _state == ParsingState.ConsumeTrailers
 0540                    ? _connection.FillForHeadersAsync(async: true)
 0541                    : _connection.FillAsync(async: true);
 0542            }
 543        }
 544    }
 545}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ChunkedEncodingWriteStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace System.Net.Http
 9{
 10    internal sealed partial class HttpConnection : IDisposable
 11    {
 12        private sealed class ChunkedEncodingWriteStream : HttpContentWriteStream
 13        {
 014            private static readonly byte[] s_crlfBytes = "\r\n"u8.ToArray();
 015            private static readonly byte[] s_finalChunkBytes = "0\r\n\r\n"u8.ToArray();
 16
 017            public ChunkedEncodingWriteStream(HttpConnection connection) : base(connection)
 018            {
 019            }
 20
 21            public override void Write(ReadOnlySpan<byte> buffer)
 022            {
 023                BytesWritten += buffer.Length;
 24
 025                HttpConnection connection = GetConnectionOrThrow();
 026                Debug.Assert(connection._currentRequest != null);
 27
 028                if (buffer.Length == 0)
 029                {
 030                    connection.Flush();
 031                    return;
 32                }
 33
 34                // Write chunk length in hex followed by \r\n
 035                ValueTask writeTask = connection.WriteHexInt32Async(buffer.Length, async: false);
 036                Debug.Assert(writeTask.IsCompleted);
 037                writeTask.GetAwaiter().GetResult();
 038                connection.Write(s_crlfBytes);
 39
 40                // Write chunk contents followed by \r\n
 041                connection.Write(buffer);
 042                connection.Write(s_crlfBytes);
 043            }
 44
 45            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken ignored)
 046            {
 047                BytesWritten += buffer.Length;
 48
 049                HttpConnection connection = GetConnectionOrThrow();
 050                Debug.Assert(connection._currentRequest != null);
 51
 52                // The token is ignored because it's coming from SendAsync and the only operations
 53                // here are those that are already covered by the token having been registered with
 54                // to close the connection.
 55
 056                ValueTask task = buffer.Length == 0 ?
 057                    // Don't write if nothing was given, especially since we don't want to accidentally send a 0 chunk,
 058                    // which would indicate end of body.  Instead, just ensure no content is stuck in the buffer.
 059                    connection.FlushAsync(async: true) :
 060                    WriteChunkAsync(connection, buffer);
 61
 062                return task;
 63
 64                static async ValueTask WriteChunkAsync(HttpConnection connection, ReadOnlyMemory<byte> buffer)
 065                {
 66                    // Write chunk length in hex followed by \r\n
 067                    await connection.WriteHexInt32Async(buffer.Length, async: true).ConfigureAwait(false);
 068                    await connection.WriteAsync(s_crlfBytes).ConfigureAwait(false);
 69
 70                    // Write chunk contents followed by \r\n
 071                    await connection.WriteAsync(buffer).ConfigureAwait(false);
 072                    await connection.WriteAsync(s_crlfBytes).ConfigureAwait(false);
 073                }
 074            }
 75
 76            public override Task FinishAsync(bool async)
 077            {
 78                // Send 0 byte chunk to indicate end, then final CrLf
 079                HttpConnection connection = GetConnectionOrThrow();
 080                _connection = null;
 81
 082                if (async)
 083                {
 084                    return connection.WriteAsync(s_finalChunkBytes).AsTask();
 85                }
 86                else
 087                {
 088                    connection.Write(s_finalChunkBytes);
 089                    return Task.CompletedTask;
 90                }
 091            }
 92        }
 93    }
 94}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectionCloseReadStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.IO;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace System.Net.Http
 9{
 10    internal sealed partial class HttpConnection : IDisposable
 11    {
 12        private sealed class ConnectionCloseReadStream : HttpContentReadStream
 13        {
 014            public ConnectionCloseReadStream(HttpConnection connection) : base(connection)
 015            {
 016            }
 17
 18            public override int Read(Span<byte> buffer)
 019            {
 020                HttpConnection? connection = _connection;
 021                if (connection == null)
 022                {
 23                    // Response body fully consumed
 024                    return 0;
 25                }
 26
 027                int bytesRead = connection.Read(buffer);
 028                if (bytesRead == 0 && buffer.Length != 0)
 029                {
 30                    // We cannot reuse this connection, so close it.
 031                    _connection = null;
 032                    connection.Dispose();
 033                }
 34
 035                return bytesRead;
 036            }
 37
 38            public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 039            {
 040                CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 41
 042                HttpConnection? connection = _connection;
 043                if (connection == null)
 044                {
 45                    // Response body fully consumed
 046                    return 0;
 47                }
 48
 049                ValueTask<int> readTask = connection.ReadAsync(buffer);
 50                int bytesRead;
 051                if (readTask.IsCompletedSuccessfully)
 052                {
 053                    bytesRead = readTask.Result;
 054                }
 55                else
 056                {
 057                    CancellationTokenRegistration ctr = connection.RegisterCancellation(cancellationToken);
 58                    try
 059                    {
 060                        bytesRead = await readTask.ConfigureAwait(false);
 061                    }
 062                    catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellat
 063                    {
 064                        throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 65                    }
 66                    finally
 067                    {
 068                        ctr.Dispose();
 069                    }
 070                }
 71
 072                if (bytesRead == 0 && buffer.Length != 0)
 073                {
 74                    // If cancellation is requested and tears down the connection, it could cause the read
 75                    // to return 0, which would otherwise signal the end of the data, but that would lead
 76                    // the caller to think that it actually received all of the data, rather than it ending
 77                    // early due to cancellation.  So we prioritize cancellation in this race condition, and
 78                    // if we read 0 bytes and then find that cancellation has requested, we assume cancellation
 79                    // was the cause and throw.
 080                    CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 81
 82                    // We cannot reuse this connection, so close it.
 083                    _connection = null;
 084                    connection.Dispose();
 085                }
 86
 087                return bytesRead;
 088            }
 89
 90            public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
 091            {
 092                ValidateCopyToArguments(destination, bufferSize);
 93
 094                if (cancellationToken.IsCancellationRequested)
 095                {
 096                    return Task.FromCanceled(cancellationToken);
 97                }
 98
 099                HttpConnection? connection = _connection;
 0100                if (connection == null)
 0101                {
 102                    // null if response body fully consumed
 0103                    return Task.CompletedTask;
 104                }
 105
 0106                Task copyTask = connection.CopyToUntilEofAsync(destination, async: true, bufferSize, cancellationToken);
 0107                if (copyTask.IsCompletedSuccessfully)
 0108                {
 0109                    Finish(connection);
 0110                    return Task.CompletedTask;
 111                }
 112
 0113                return CompleteCopyToAsync(copyTask, connection, cancellationToken);
 0114            }
 115
 116            private async Task CompleteCopyToAsync(Task copyTask, HttpConnection connection, CancellationToken cancellat
 0117            {
 0118                CancellationTokenRegistration ctr = connection.RegisterCancellation(cancellationToken);
 119                try
 0120                {
 0121                    await copyTask.ConfigureAwait(false);
 0122                }
 0123                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0124                {
 0125                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 126                }
 127                finally
 0128                {
 0129                    ctr.Dispose();
 0130                }
 131
 132                // If cancellation is requested and tears down the connection, it could cause the copy
 133                // to end early but think it ended successfully. So we prioritize cancellation in this
 134                // race condition, and if we find after the copy has completed that cancellation has
 135                // been requested, we assume the copy completed due to cancellation and throw.
 0136                CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 137
 0138                Finish(connection);
 0139            }
 140
 141            private void Finish(HttpConnection connection)
 0142            {
 143                // We cannot reuse this connection, so close it.
 0144                _connection = null;
 0145                connection.Dispose();
 0146            }
 147        }
 148    }
 149}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ContentLengthReadStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.IO;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.Net.Http
 10{
 11    internal sealed partial class HttpConnection : IDisposable
 12    {
 13        private sealed class ContentLengthReadStream : HttpContentReadStream
 14        {
 15            private ulong _contentBytesRemaining;
 16
 017            public ContentLengthReadStream(HttpConnection connection, ulong contentLength) : base(connection)
 018            {
 019                Debug.Assert(contentLength > 0, "Caller should have checked for 0.");
 020                _contentBytesRemaining = contentLength;
 021            }
 22
 23            public override int Read(Span<byte> buffer)
 024            {
 025                if (_connection == null)
 026                {
 27                    // Response body fully consumed
 028                    return 0;
 29                }
 30
 031                Debug.Assert(_contentBytesRemaining > 0);
 032                if ((ulong)buffer.Length > _contentBytesRemaining)
 033                {
 034                    buffer = buffer.Slice(0, (int)_contentBytesRemaining);
 035                }
 36
 037                int bytesRead = _connection.Read(buffer);
 038                if (bytesRead <= 0 && buffer.Length != 0)
 039                {
 40                    // Unexpected end of response stream.
 041                    throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_pre
 42                }
 43
 044                Debug.Assert((ulong)bytesRead <= _contentBytesRemaining);
 045                _contentBytesRemaining -= (ulong)bytesRead;
 46
 047                if (_contentBytesRemaining == 0)
 048                {
 49                    // End of response body
 050                    _connection.CompleteResponse();
 051                    _connection = null;
 052                }
 53
 054                return bytesRead;
 055            }
 56
 57            public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 058            {
 059                CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 60
 061                if (_connection == null)
 062                {
 63                    // Response body fully consumed
 064                    return 0;
 65                }
 66
 067                Debug.Assert(_contentBytesRemaining > 0);
 68
 069                if ((ulong)buffer.Length > _contentBytesRemaining)
 070                {
 071                    buffer = buffer.Slice(0, (int)_contentBytesRemaining);
 072                }
 73
 074                ValueTask<int> readTask = _connection.ReadAsync(buffer);
 75                int bytesRead;
 076                if (readTask.IsCompletedSuccessfully)
 077                {
 078                    bytesRead = readTask.Result;
 079                }
 80                else
 081                {
 082                    CancellationTokenRegistration ctr = _connection.RegisterCancellation(cancellationToken);
 83                    try
 084                    {
 085                        bytesRead = await readTask.ConfigureAwait(false);
 086                    }
 087                    catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellat
 088                    {
 089                        throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 90                    }
 91                    finally
 092                    {
 093                        ctr.Dispose();
 094                    }
 095                }
 96
 097                if (bytesRead == 0 && buffer.Length != 0)
 098                {
 99                    // A cancellation request may have caused the EOF.
 0100                    CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 101
 102                    // Unexpected end of response stream.
 0103                    throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_pre
 104                }
 105
 0106                Debug.Assert((ulong)bytesRead <= _contentBytesRemaining);
 0107                _contentBytesRemaining -= (ulong)bytesRead;
 108
 0109                if (_contentBytesRemaining == 0)
 0110                {
 111                    // End of response body
 0112                    _connection.CompleteResponse();
 0113                    _connection = null;
 0114                }
 115
 0116                return bytesRead;
 0117            }
 118
 119            public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
 0120            {
 0121                ValidateCopyToArguments(destination, bufferSize);
 122
 0123                if (cancellationToken.IsCancellationRequested)
 0124                {
 0125                    return Task.FromCanceled(cancellationToken);
 126                }
 127
 0128                if (_connection == null)
 0129                {
 130                    // null if response body fully consumed
 0131                    return Task.CompletedTask;
 132                }
 133
 0134                Task copyTask = _connection.CopyToContentLengthAsync(destination, async: true, _contentBytesRemaining, b
 0135                if (copyTask.IsCompletedSuccessfully)
 0136                {
 0137                    Finish();
 0138                    return Task.CompletedTask;
 139                }
 140
 0141                return CompleteCopyToAsync(copyTask, cancellationToken);
 0142            }
 143
 144            private async Task CompleteCopyToAsync(Task copyTask, CancellationToken cancellationToken)
 0145            {
 0146                Debug.Assert(_connection != null);
 0147                CancellationTokenRegistration ctr = _connection.RegisterCancellation(cancellationToken);
 148                try
 0149                {
 0150                    await copyTask.ConfigureAwait(false);
 0151                }
 0152                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0153                {
 0154                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 155                }
 156                finally
 0157                {
 0158                    ctr.Dispose();
 0159                }
 160
 0161                Finish();
 0162            }
 163
 164            private void Finish()
 0165            {
 0166                _contentBytesRemaining = 0;
 0167                _connection!.CompleteResponse();
 0168                _connection = null;
 0169            }
 170
 171            // Based on ReadChunkFromConnectionBuffer; perhaps we should refactor into a common routine.
 172            private ReadOnlyMemory<byte> ReadFromConnectionBuffer(int maxBytesToRead)
 0173            {
 0174                Debug.Assert(maxBytesToRead > 0);
 0175                Debug.Assert(_contentBytesRemaining > 0);
 0176                Debug.Assert(_connection != null);
 177
 0178                ReadOnlyMemory<byte> connectionBuffer = _connection.RemainingBuffer;
 0179                if (connectionBuffer.Length == 0)
 0180                {
 0181                    return default;
 182                }
 183
 0184                int bytesToConsume = Math.Min(maxBytesToRead, (int)Math.Min((ulong)connectionBuffer.Length, _contentByte
 0185                Debug.Assert(bytesToConsume > 0);
 186
 0187                _connection.ConsumeFromRemainingBuffer(bytesToConsume);
 0188                _contentBytesRemaining -= (ulong)bytesToConsume;
 189
 0190                return connectionBuffer.Slice(0, bytesToConsume);
 0191            }
 192
 0193            public override bool NeedsDrain => CanReadFromConnection;
 194
 195            public override async ValueTask<bool> DrainAsync(int maxDrainBytes)
 0196            {
 0197                Debug.Assert(_connection != null);
 0198                Debug.Assert(_contentBytesRemaining > 0);
 199
 0200                ReadFromConnectionBuffer(int.MaxValue);
 0201                if (_contentBytesRemaining == 0)
 0202                {
 0203                    Finish();
 0204                    return true;
 205                }
 206
 0207                if (_contentBytesRemaining > (ulong)maxDrainBytes)
 0208                {
 0209                    return false;
 210                }
 211
 0212                CancellationTokenSource? cts = null;
 0213                CancellationTokenRegistration ctr = default;
 0214                TimeSpan drainTime = _connection._pool.Settings._maxResponseDrainTime;
 215
 0216                if (drainTime == TimeSpan.Zero)
 0217                {
 0218                    return false;
 219                }
 220
 0221                if (drainTime != Timeout.InfiniteTimeSpan)
 0222                {
 0223                    cts = new CancellationTokenSource((int)drainTime.TotalMilliseconds);
 0224                    ctr = cts.Token.Register(static s => ((HttpConnection)s!).Dispose(), _connection);
 0225                }
 226
 227                try
 0228                {
 0229                    while (true)
 0230                    {
 0231                        await _connection.FillAsync(async: true).ConfigureAwait(false);
 0232                        ReadFromConnectionBuffer(int.MaxValue);
 0233                        if (_contentBytesRemaining == 0)
 0234                        {
 235                            // Dispose of the registration and then check whether cancellation has been
 236                            // requested. This is necessary to make deterministic a race condition between
 237                            // cancellation being requested and unregistering from the token.  Otherwise,
 238                            // it's possible cancellation could be requested just before we unregister and
 239                            // we then return a connection to the pool that has been or will be disposed
 240                            // (e.g. if a timer is used and has already queued its callback but the
 241                            // callback hasn't yet run).
 0242                            ctr.Dispose();
 0243                            CancellationHelper.ThrowIfCancellationRequested(ctr.Token);
 244
 0245                            Finish();
 0246                            return true;
 247                        }
 0248                    }
 249                }
 250                finally
 0251                {
 0252                    ctr.Dispose();
 0253                    cts?.Dispose();
 0254                }
 0255            }
 256        }
 257    }
 258}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ContentLengthWriteStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Runtime.ExceptionServices;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.Net.Http
 10{
 11    internal sealed partial class HttpConnection : IDisposable
 12    {
 13        private sealed class ContentLengthWriteStream : HttpContentWriteStream
 14        {
 15            private readonly long _contentLength;
 16
 17            public ContentLengthWriteStream(HttpConnection connection, long contentLength)
 018                : base(connection)
 019            {
 020                _contentLength = contentLength;
 021            }
 22
 23            public override void Write(ReadOnlySpan<byte> buffer)
 024            {
 025                BytesWritten += buffer.Length;
 26
 027                if (BytesWritten > _contentLength)
 028                {
 029                    throw new HttpRequestException(SR.net_http_content_write_larger_than_content_length);
 30                }
 31
 032                HttpConnection connection = GetConnectionOrThrow();
 033                Debug.Assert(connection._currentRequest != null);
 034                connection.Write(buffer);
 035            }
 36
 37            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken ignored) // token ignore
 038            {
 039                BytesWritten += buffer.Length;
 40
 041                if (BytesWritten > _contentLength)
 042                {
 043                    return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new HttpRequestException(S
 44                }
 45
 046                HttpConnection connection = GetConnectionOrThrow();
 047                Debug.Assert(connection._currentRequest != null);
 048                return connection.WriteAsync(buffer);
 049            }
 50
 51            public override Task FinishAsync(bool async)
 052            {
 053                if (BytesWritten != _contentLength)
 054                {
 055                    return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new HttpRequestException(SR.For
 56                }
 57
 058                _connection = null;
 059                return Task.CompletedTask;
 060            }
 61        }
 62    }
 63}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnection.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Text;
 6using System.Diagnostics;
 7using System.Globalization;
 8using System.IO;
 9using System.Net.Http.Headers;
 10using System.Net.Sockets;
 11using System.Runtime.CompilerServices;
 12using System.Text;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace System.Net.Http
 17{
 18    internal sealed partial class HttpConnection : HttpConnectionBase
 19    {
 20        /// <summary>Default size of the read buffer used for the connection.</summary>
 21        private const int InitialReadBufferSize =
 22#if DEBUG
 23            10;
 24#else
 25            4096;
 26#endif
 27        /// <summary>Default size of the write buffer used for the connection.</summary>
 28        private const int InitialWriteBufferSize = InitialReadBufferSize;
 29        /// <summary>
 30        /// Size after which we'll close the connection rather than send the payload in response
 31        /// to final error status code sent by the server when using Expect: 100-continue.
 32        /// </summary>
 33        private const int Expect100ErrorSendThreshold = 1024;
 34        /// <summary>How long a chunk indicator is allowed to be.</summary>
 35        /// <remarks>
 36        /// While most chunks indicators will contain no more than ulong.MaxValue.ToString("X").Length characters,
 37        /// "chunk extensions" are allowed. We place a limit on how long a line can be to avoid OOM issues if an
 38        /// infinite chunk length is sent.  This value is arbitrary and can be changed as needed.
 39        /// </remarks>
 40        private const int MaxChunkBytesAllowed = 16 * 1024;
 41
 042        private static readonly ulong s_http10Bytes = BitConverter.ToUInt64("HTTP/1.0"u8);
 043        private static readonly ulong s_http11Bytes = BitConverter.ToUInt64("HTTP/1.1"u8);
 44
 45        internal readonly Stream _stream;
 46        private readonly TransportContext? _transportContext;
 47
 48        private HttpRequestMessage? _currentRequest;
 49        private ArrayBuffer _writeBuffer;
 50        private int _allowedReadLineBytes;
 51
 52        /// <summary>Reusable array used to get the values for each header being written to the wire.</summary>
 53        [ThreadStatic]
 54        private static string[]? t_headerValues;
 55
 56        private const int ReadAheadTask_NotStarted = 0;
 57        private const int ReadAheadTask_Started = 1;
 58        private const int ReadAheadTask_CompletionReserved = 2;
 59        private const int ReadAheadTask_Completed = 3;
 60        private int _readAheadTaskStatus;
 61        private ValueTask<int> _readAheadTask;
 62        private ArrayBuffer _readBuffer;
 63
 64        private int _keepAliveTimeoutSeconds; // 0 == no timeout
 65        private bool _inUse;
 66        private bool _detachedFromPool;
 67        private bool _canRetry;
 68        private bool _connectionClose; // Connection: close was seen on last response
 69
 70        private volatile bool _disposed;
 71
 72        public HttpConnection(
 73            HttpConnectionPool pool,
 74            Stream stream,
 75            TransportContext? transportContext,
 76            Activity? connectionSetupActivity,
 77            IPEndPoint? remoteEndPoint)
 078            : base(pool, connectionSetupActivity, remoteEndPoint)
 079        {
 080            Debug.Assert(stream != null);
 81
 082            _stream = stream;
 83
 084            _transportContext = transportContext;
 85
 086            _writeBuffer = new ArrayBuffer(InitialWriteBufferSize, usePool: false);
 087            _readBuffer = new ArrayBuffer(InitialReadBufferSize, usePool: false);
 88
 089            if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
 090        }
 91
 092        ~HttpConnection() => Dispose(disposing: false);
 93
 094        public override void Dispose() => Dispose(disposing: true);
 95
 96        private void Dispose(bool disposing)
 097        {
 98            // Ensure we're only disposed once.  Dispose could be called concurrently, for example,
 99            // if the request and the response were running concurrently and both incurred an exception.
 0100            if (!Interlocked.Exchange(ref _disposed, true))
 0101            {
 0102                if (NetEventSource.Log.IsEnabled()) Trace("Connection closing.");
 103
 0104                MarkConnectionAsClosed();
 105
 0106                if (!_detachedFromPool)
 0107                {
 0108                    _pool.InvalidateHttp11Connection(this, disposing);
 0109                }
 110
 0111                if (disposing)
 0112                {
 0113                    GC.SuppressFinalize(this);
 0114                    _stream.Dispose();
 0115                }
 0116            }
 0117        }
 118
 119        private bool ReadAheadTaskHasStarted =>
 0120            _readAheadTaskStatus != ReadAheadTask_NotStarted;
 121
 122        /// <summary>Prepare an idle connection to be used for a new request.
 123        /// The caller MUST call SendAsync afterwards if this method returns true, or dispose the connection if it retur
 124        /// <param name="async">Indicates whether the coming request will be sync or async.</param>
 125        /// <returns>True if connection can be used, false if it is invalid due to a timeout or receiving EOF or unexpec
 126        public bool PrepareForReuse(bool async)
 0127        {
 0128            if (CheckKeepAliveTimeoutExceeded())
 0129            {
 0130                return false;
 131            }
 132
 133            // We may already have a read-ahead task if we did a previous scavenge and haven't used the connection since
 134            // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so 
 0135            if (ReadAheadTaskHasStarted)
 0136            {
 0137                Debug.Assert(_readAheadTaskStatus is ReadAheadTask_Started or ReadAheadTask_Completed);
 138
 0139                return Interlocked.Exchange(ref _readAheadTaskStatus, ReadAheadTask_CompletionReserved) == ReadAheadTask
 140            }
 141
 142            // Check to see if we've received anything on the connection; if we have, that's
 143            // either erroneous data (we shouldn't have received anything yet) or the connection
 144            // has been closed; either way, we can't use it.
 0145            if (!async && _stream is NetworkStream networkStream)
 0146            {
 147                // Directly poll the socket rather than doing an async read, so that we can
 148                // issue an appropriate sync read when we actually need it.
 149                try
 0150                {
 0151                    return !networkStream.Socket.Poll(0, SelectMode.SelectRead);
 152                }
 0153                catch (Exception e) when (e is SocketException || e is ObjectDisposedException)
 0154                {
 155                    // Poll can throw when used on a closed socket.
 0156                    return false;
 157                }
 158            }
 159            else
 0160            {
 0161                Debug.Assert(_readAheadTaskStatus == ReadAheadTask_NotStarted);
 0162                _readAheadTaskStatus = ReadAheadTask_CompletionReserved;
 163
 164                // Perform an async read on the stream, since we're going to need to read from it
 165                // anyway, and in doing so we can avoid the extra syscall.
 166                try
 0167                {
 168#pragma warning disable CA2012 // we're very careful to ensure the ValueTask is only consumed once, even though it's sto
 0169                    _readAheadTask = _stream.ReadAsync(_readBuffer.AvailableMemory);
 170#pragma warning restore CA2012
 171
 172                    // If the read-ahead task already completed, we can't reuse the connection.
 173                    // We're still responsible for observing potential exceptions thrown by the read-ahead task to avoid
 0174                    if (_readAheadTask.IsCompleted)
 0175                    {
 0176                        LogExceptions(_readAheadTask.AsTask());
 0177                        return false;
 178                    }
 179
 0180                    return true;
 181                }
 0182                catch (Exception error)
 0183                {
 184                    // If reading throws, eat the error and don't reuse the connection.
 0185                    if (NetEventSource.Log.IsEnabled()) Trace($"Error performing read ahead: {error}");
 0186                    return false;
 187                }
 188            }
 0189        }
 190
 191        /// <summary>Takes ownership of the scavenging task completion if it was started.
 192        /// The caller MUST call either SendAsync or return the completion ownership afterwards if this method returns t
 193        public bool TryOwnScavengingTaskCompletion()
 0194        {
 0195            Debug.Assert(_readAheadTaskStatus != ReadAheadTask_CompletionReserved);
 196
 0197            return !ReadAheadTaskHasStarted
 0198                || Interlocked.Exchange(ref _readAheadTaskStatus, ReadAheadTask_CompletionReserved) == ReadAheadTask_Sta
 0199        }
 200
 201        /// <summary>Returns ownership of the scavenging task completion if it was started.
 202        /// The caller MUST Dispose the connection afterwards if this method returns false.</summary>
 203        public bool TryReturnScavengingTaskCompletionOwnership()
 0204        {
 0205            Debug.Assert(_readAheadTaskStatus != ReadAheadTask_Started);
 206
 0207            if (!ReadAheadTaskHasStarted ||
 0208                Interlocked.Exchange(ref _readAheadTaskStatus, ReadAheadTask_Started) == ReadAheadTask_CompletionReserve
 0209            {
 0210                return true;
 211            }
 212
 213            // The read-ahead task has started, and we failed to transition back to Started.
 214            // This means that the read-ahead task has completed, and we can't reuse the connection. The caller must dis
 215            // We're still responsible for observing potential exceptions thrown by the read-ahead task to avoid leaking
 0216            LogExceptions(_readAheadTask.AsTask());
 0217            return false;
 0218        }
 219
 220        /// <summary>Check whether a currently idle connection is still usable, or should be scavenged.</summary>
 221        /// <returns>True if connection can be used, false if it is invalid due to a timeout or receiving EOF or unexpec
 222        public override bool CheckUsabilityOnScavenge()
 0223        {
 0224            if (CheckKeepAliveTimeoutExceeded())
 0225            {
 0226                return false;
 227            }
 228
 229            // We may already have a read-ahead task if we did a previous scavenge and haven't used the connection since
 0230            if (!ReadAheadTaskHasStarted)
 0231            {
 0232                Debug.Assert(_readAheadTask == default);
 233
 0234                _readAheadTaskStatus = ReadAheadTask_Started;
 235
 236#pragma warning disable CA2012 // we're very careful to ensure the ValueTask is only consumed once, even though it's sto
 0237                _readAheadTask = ReadAheadWithZeroByteReadAsync();
 238#pragma warning restore CA2012
 0239            }
 240
 241            // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so 
 0242            return !_readAheadTask.IsCompleted;
 243
 244            async ValueTask<int> ReadAheadWithZeroByteReadAsync()
 0245            {
 0246                Debug.Assert(_readAheadTask == default);
 0247                Debug.Assert(_readBuffer.ActiveLength == 0);
 248
 249                try
 0250                {
 251                    // Issue a zero-byte read.
 252                    // If the underlying stream supports it, this will not complete until the stream has data available,
 253                    // which will avoid pinning the connection's read buffer (and possibly allow us to release it to the
 254                    // If not, it will complete immediately.
 0255                    await _stream.ReadAsync(Memory<byte>.Empty).ConfigureAwait(false);
 256
 257                    // We don't know for sure that the stream actually has data available, so we need to issue a real re
 0258                    int read = await _stream.ReadAsync(_readBuffer.AvailableMemory).ConfigureAwait(false);
 259
 260                    // PrepareForReuse will check TryOwnReadAheadTaskCompletion before calling into SendAsync.
 261                    // If we can own the completion from within the read-ahead task, it means that PrepareForReuse hasn'
 262                    // In that case we've received EOF/erroneous data before we sent the request headers, and the connec
 0263                    if (TransitionToCompletedAndTryOwnCompletion())
 0264                    {
 0265                        if (NetEventSource.Log.IsEnabled()) Trace("Read-ahead task observed data before the request was 
 0266                    }
 267
 0268                    return read;
 269                }
 0270                catch (Exception error) when (TransitionToCompletedAndTryOwnCompletion())
 0271                {
 0272                    if (NetEventSource.Log.IsEnabled()) Trace($"Error performing read ahead: {error}");
 273
 0274                    return 0;
 275                }
 276
 277                bool TransitionToCompletedAndTryOwnCompletion()
 0278                {
 0279                    Debug.Assert(_readAheadTaskStatus is ReadAheadTask_Started or ReadAheadTask_CompletionReserved);
 280
 0281                    return Interlocked.Exchange(ref _readAheadTaskStatus, ReadAheadTask_Completed) == ReadAheadTask_Star
 0282                }
 0283            }
 0284        }
 285
 286        private bool CheckKeepAliveTimeoutExceeded()
 0287        {
 288            // We intentionally honor the Keep-Alive timeout on all HTTP/1.X versions, not just 1.0. This is to maximize
 289            // servers that use a lower idle timeout than the client, but give us a hint in the form of a Keep-Alive tim
 290            // If _keepAliveTimeoutSeconds is 0, no timeout has been set.
 0291            return _keepAliveTimeoutSeconds != 0 &&
 0292                GetIdleTicks(Environment.TickCount64) >= _keepAliveTimeoutSeconds * 1000;
 0293        }
 294
 0295        public TransportContext? TransportContext => _transportContext;
 296
 0297        public HttpConnectionKind Kind => _pool.Kind;
 298
 0299        private int ReadBufferSize => _readBuffer.Capacity;
 300
 0301        private ReadOnlyMemory<byte> RemainingBuffer => _readBuffer.ActiveMemory;
 302
 303        private void ConsumeFromRemainingBuffer(int bytesToConsume)
 0304        {
 0305            Debug.Assert(bytesToConsume <= _readBuffer.ActiveLength);
 0306            _readBuffer.Discard(bytesToConsume);
 0307        }
 308
 309        private void WriteHeaders(HttpRequestMessage request)
 0310        {
 0311            Debug.Assert(request.RequestUri is not null);
 312
 313            // Write the request line
 0314            WriteBytes(request.Method.Http1EncodedBytes);
 315
 0316            if (request.Method.IsConnect)
 0317            {
 318                // RFC 7231 #section-4.3.6.
 319                // Write only CONNECT foo.com:345 HTTP/1.1
 0320                if (!request.HasHeaders || request.Headers.Host is not string host)
 0321                {
 0322                    throw new HttpRequestException(SR.net_http_request_no_host);
 323                }
 324
 0325                WriteAsciiString(host);
 0326            }
 327            else
 0328            {
 0329                if (Kind == HttpConnectionKind.Proxy)
 0330                {
 331                    // Proxied requests contain full URL
 0332                    Debug.Assert(request.RequestUri.Scheme == Uri.UriSchemeHttp);
 0333                    WriteBytes("http://"u8);
 0334                    WriteHost(request.RequestUri);
 0335                }
 336
 0337                WriteAsciiString(request.RequestUri.PathAndQuery);
 0338            }
 339
 340            // Fall back to 1.1 for all versions other than 1.0
 0341            Debug.Assert(request.Version.Major >= 0 && request.Version.Minor >= 0); // guaranteed by Version class
 0342            bool isHttp10 = request.Version.Minor == 0 && request.Version.Major == 1;
 0343            WriteBytes(isHttp10 ? " HTTP/1.0\r\n"u8 : " HTTP/1.1\r\n"u8);
 344
 345            // Write special additional headers.  If a host isn't in the headers list, then a Host header
 346            // wasn't set, so as it's required by HTTP 1.1 spec, send one based on the Request Uri.
 0347            if (!request.HasHeaders || request.Headers.Host is null)
 0348            {
 0349                if (_pool.HostHeaderLineBytes is byte[] hostHeaderLineBytes)
 0350                {
 0351                    Debug.Assert(Kind != HttpConnectionKind.Proxy);
 0352                    WriteBytes(hostHeaderLineBytes);
 0353                }
 354                else
 0355                {
 0356                    Debug.Assert(Kind == HttpConnectionKind.Proxy);
 0357                    WriteBytes(KnownHeaders.Host.AsciiBytesWithColonSpace);
 0358                    WriteHost(request.RequestUri);
 0359                    WriteCRLF();
 0360                }
 0361            }
 362
 363            // Determine cookies to send
 0364            string? cookiesFromContainer = null;
 0365            if (_pool.Settings._useCookies)
 0366            {
 0367                cookiesFromContainer = _pool.Settings._cookieContainer!.GetCookieHeader(request.RequestUri);
 0368                if (cookiesFromContainer == "")
 0369                {
 0370                    cookiesFromContainer = null;
 0371                }
 0372            }
 373
 374            // Write request headers
 0375            if (request.HasHeaders || cookiesFromContainer is not null)
 0376            {
 0377                WriteHeaderCollection(request.Headers, cookiesFromContainer);
 0378            }
 379
 380            // Write content headers
 0381            if (request.Content is HttpContent content)
 0382            {
 0383                WriteHeaderCollection(content.Headers);
 0384            }
 385            else
 0386            {
 387                // Write out Content-Length: 0 header to indicate no body,
 388                // unless this is a method that never has a body.
 0389                if (request.Method.MustHaveRequestBody)
 0390                {
 0391                    WriteBytes("Content-Length: 0\r\n"u8);
 0392                }
 0393            }
 394
 395            // CRLF for end of headers.
 0396            WriteCRLF();
 397
 398            void WriteHost(Uri requestUri)
 0399            {
 400                // Uri.IdnHost is missing '[', ']' characters around IPv6 address
 401                // and it also contains ScopeID for Link-Local addresses
 0402                string host = requestUri.HostNameType == UriHostNameType.IPv6 ? requestUri.Host : requestUri.IdnHost;
 0403                WriteAsciiString(host);
 404
 0405                if (!requestUri.IsDefaultPort)
 0406                {
 0407                    _writeBuffer.EnsureAvailableSpace(6);
 0408                    Span<byte> buffer = _writeBuffer.AvailableSpan;
 0409                    buffer[0] = (byte)':';
 0410                    bool success = ((uint)requestUri.Port).TryFormat(buffer.Slice(1), out int bytesWritten);
 0411                    Debug.Assert(success);
 0412                    _writeBuffer.Commit(bytesWritten + 1);
 0413                }
 0414            }
 0415        }
 416
 417        private void WriteHeaderCollection(HttpHeaders headers, string? cookiesFromContainer = null)
 0418        {
 0419            Debug.Assert(_currentRequest is not null);
 420
 0421            HeaderEncodingSelector<HttpRequestMessage>? encodingSelector = _pool.Settings._requestHeaderEncodingSelector
 0422            ref string[]? headerValues = ref t_headerValues;
 423
 0424            foreach (HeaderEntry header in headers.GetEntries())
 0425            {
 0426                if (header.Key.KnownHeader is KnownHeader knownHeader)
 0427                {
 0428                    WriteBytes(knownHeader.AsciiBytesWithColonSpace);
 0429                }
 430                else
 0431                {
 0432                    WriteAsciiString(header.Key.Name);
 0433                    WriteBytes(": "u8);
 0434                }
 435
 0436                int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref headerVa
 0437                Debug.Assert(headerValuesCount > 0, "No values for header??");
 438
 0439                Encoding? valueEncoding = encodingSelector?.Invoke(header.Key.Name, _currentRequest);
 440
 0441                WriteString(headerValues[0], valueEncoding);
 442
 0443                if (cookiesFromContainer is not null && header.Key.Equals(KnownHeaders.Cookie))
 0444                {
 0445                    WriteBytes("; "u8); // Cookies use "; " as the separator
 0446                    WriteString(cookiesFromContainer, valueEncoding);
 0447                    cookiesFromContainer = null;
 0448                }
 449
 450                // Some headers such as User-Agent and Server use space as a separator (see: ProductInfoHeaderParser)
 0451                if (headerValuesCount > 1)
 0452                {
 0453                    byte[] separator = header.Key.SeparatorBytes;
 454
 0455                    for (int i = 1; i < headerValuesCount; i++)
 0456                    {
 0457                        WriteBytes(separator);
 0458                        WriteString(headerValues[i], valueEncoding);
 0459                    }
 0460                }
 461
 0462                WriteCRLF();
 0463            }
 464
 0465            if (cookiesFromContainer is not null)
 0466            {
 0467                WriteBytes(KnownHeaders.Cookie.AsciiBytesWithColonSpace);
 0468                WriteString(cookiesFromContainer, encodingSelector?.Invoke(HttpKnownHeaderNames.Cookie, _currentRequest)
 0469                WriteCRLF();
 0470            }
 0471        }
 472
 473        private void WriteCRLF()
 0474        {
 0475            _writeBuffer.EnsureAvailableSpace(2);
 0476            Span<byte> buffer = _writeBuffer.AvailableSpan;
 0477            buffer[1] = (byte)'\n';
 0478            buffer[0] = (byte)'\r';
 0479            _writeBuffer.Commit(2);
 0480        }
 481
 482        private void WriteBytes(ReadOnlySpan<byte> bytes)
 0483        {
 0484            _writeBuffer.EnsureAvailableSpace(bytes.Length);
 0485            bytes.CopyTo(_writeBuffer.AvailableSpan);
 0486            _writeBuffer.Commit(bytes.Length);
 0487        }
 488
 489        private void WriteAsciiString(string s)
 0490        {
 0491            Debug.Assert(Ascii.IsValid(s));
 492
 0493            _writeBuffer.EnsureAvailableSpace(s.Length);
 494
 0495            OperationStatus status = Ascii.FromUtf16(s, _writeBuffer.AvailableSpan, out int bytesWritten);
 0496            Debug.Assert(status == OperationStatus.Done);
 0497            Debug.Assert(bytesWritten == s.Length);
 498
 0499            _writeBuffer.Commit(s.Length);
 0500        }
 501
 502        private void WriteString(string s, Encoding? encoding)
 0503        {
 0504            if (encoding is null)
 0505            {
 0506                _writeBuffer.EnsureAvailableSpace(s.Length);
 0507                Span<byte> buffer = _writeBuffer.AvailableSpan;
 508
 0509                OperationStatus status = Ascii.FromUtf16(s, buffer, out int bytesWritten);
 510
 0511                if (status == OperationStatus.InvalidData)
 0512                {
 0513                    ThrowForInvalidCharEncoding();
 0514                }
 515
 0516                Debug.Assert(status == OperationStatus.Done);
 0517                Debug.Assert(bytesWritten == s.Length);
 518
 0519                _writeBuffer.Commit(s.Length);
 0520            }
 521            else
 0522            {
 0523                _writeBuffer.EnsureAvailableSpace(encoding.GetMaxByteCount(s.Length));
 0524                int length = encoding.GetBytes(s, _writeBuffer.AvailableSpan);
 0525                _writeBuffer.Commit(length);
 0526            }
 527
 528            static void ThrowForInvalidCharEncoding() =>
 0529                throw new HttpRequestException(SR.net_http_request_invalid_char_encoding);
 0530        }
 531
 532        public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cance
 0533        {
 0534            Debug.Assert(_currentRequest == null, $"Expected null {nameof(_currentRequest)}.");
 0535            Debug.Assert(_readBuffer.ActiveLength == 0, "Unexpected data in read buffer");
 0536            Debug.Assert(_readAheadTaskStatus != ReadAheadTask_Started,
 0537                "The caller should have called PrepareForReuse or TryOwnScavengingTaskCompletion if the connection was i
 538
 0539            MarkConnectionAsNotIdle();
 540
 0541            TaskCompletionSource<bool>? allowExpect100ToContinue = null;
 0542            Task? sendRequestContentTask = null;
 543
 0544            _currentRequest = request;
 545
 0546            _canRetry = false;
 547
 548            // Send the request.
 0549            if (NetEventSource.Log.IsEnabled()) Trace($"Sending request: {request}");
 0550            if (ConnectionSetupActivity is not null) ConnectionSetupDistributedTracing.AddConnectionLinkToRequestActivit
 0551            CancellationTokenRegistration cancellationRegistration = RegisterCancellation(cancellationToken);
 552            try
 0553            {
 0554                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(Id);
 555
 0556                WriteHeaders(request);
 557
 0558                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop();
 559
 0560                if (request.Content == null)
 0561                {
 562                    // We have nothing more to send, so flush out any headers we haven't yet sent.
 0563                    await FlushAsync(async).ConfigureAwait(false);
 0564                }
 565                else
 0566                {
 0567                    bool hasExpectContinueHeader = request.HasHeaders && request.Headers.ExpectContinue == true;
 0568                    if (NetEventSource.Log.IsEnabled()) Trace($"Request content is not null, start processing it. hasExp
 569
 570                    // Send the body if there is one.  We prefer to serialize the sending of the content before
 571                    // we try to receive any response, but if ExpectContinue has been set, we allow the sending
 572                    // to run concurrently until we receive the final status line, at which point we wait for it.
 0573                    if (!hasExpectContinueHeader)
 0574                    {
 0575                        await SendRequestContentAsync(request, CreateRequestContentStream(request), async, cancellationT
 0576                    }
 577                    else
 0578                    {
 579                        // We're sending an Expect: 100-continue header. We need to flush headers so that the server rec
 580                        // all of them, and we need to do so before initiating the send, as once we do that, it effectiv
 581                        // owns the right to write, and we don't want to concurrently be accessing the write buffer.
 0582                        await FlushAsync(async).ConfigureAwait(false);
 583
 584                        // Create a TCS we'll use to block the request content from being sent, and create a timer that'
 585                        // as a fail-safe to unblock the request content if we don't hear back from the server in a time
 586                        // Then kick off the request.  The TCS' result indicates whether content should be sent or not.
 0587                        allowExpect100ToContinue = new TaskCompletionSource<bool>();
 0588                        var expect100Timer = new Timer(
 589                            static s => ((TaskCompletionSource<bool>)s!).TrySetResult(true),
 0590                            allowExpect100ToContinue, _pool.Settings._expect100ContinueTimeout, Timeout.InfiniteTimeSpan
 591#pragma warning disable CA2025
 0592                        sendRequestContentTask = SendRequestContentWithExpect100ContinueAsync(
 0593                            request, allowExpect100ToContinue.Task, CreateRequestContentStream(request), expect100Timer,
 594#pragma warning restore
 0595                    }
 0596                }
 597
 598                // Start to read response.
 0599                _allowedReadLineBytes = _pool.Settings.MaxResponseHeadersByteLength;
 600
 601                // We should not have any buffered data here; if there was, it should have been treated as an error
 602                // by the previous request handling.  (Note we do not support HTTP pipelining.)
 0603                Debug.Assert(_readBuffer.ActiveLength == 0);
 604
 605                // When the connection was taken out of the pool, a pre-emptive read was performed
 606                // into the read buffer. We need to consume that read prior to issuing another read.
 0607                if (ReadAheadTaskHasStarted)
 0608                {
 609                    // If the read-ahead task completed synchronously, it would have claimed ownership of its completion
 610                    // meaning that PrepareForReuse would have failed, and we wouldn't have called SendAsync.
 611                    // The task therefore shouldn't be 'default', as it's representing an async operation that had to yi
 0612                    Debug.Assert(_readAheadTask != default);
 0613                    Debug.Assert(_readAheadTaskStatus is ReadAheadTask_CompletionReserved or ReadAheadTask_Completed);
 614
 615                    // Handle the pre-emptive read.  For the async==false case, hopefully the read has
 616                    // already completed and this will be a nop, but if it hasn't, the caller will be forced to block
 617                    // waiting for the async operation to complete.  We will only hit this case for proxied HTTPS
 618                    // requests that use a pooled connection, as in that case we don't have a Socket we
 619                    // can poll and are forced to issue an async read.
 0620                    ValueTask<int> vt = _readAheadTask;
 0621                    _readAheadTask = default;
 622
 623                    int bytesRead;
 0624                    if (vt.IsCompleted)
 0625                    {
 0626                        bytesRead = vt.Result;
 0627                    }
 628                    else
 0629                    {
 0630                        if (NetEventSource.Log.IsEnabled() && !async)
 0631                        {
 0632                            Trace($"Pre-emptive read completed asynchronously for a synchronous request.");
 0633                        }
 634
 0635                        bytesRead = await vt.ConfigureAwait(false);
 0636                    }
 637
 0638                    _readBuffer.Commit(bytesRead);
 639
 0640                    if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
 641
 0642                    _readAheadTaskStatus = ReadAheadTask_NotStarted;
 0643                }
 644                else
 0645                {
 646                    // No read-ahead, so issue a read ourselves. We will check below for EOF.
 0647                    await InitialFillAsync(async).ConfigureAwait(false);
 0648                }
 649
 0650                if (_readBuffer.ActiveLength == 0)
 0651                {
 652                    // The server shutdown the connection on their end, likely because of an idle timeout.
 653                    // If we haven't started sending the request body yet (or there is no request body),
 654                    // then we allow the request to be retried.
 0655                    if (request.Content is null || allowExpect100ToContinue is not null)
 0656                    {
 0657                        _canRetry = true;
 0658                    }
 659
 0660                    throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof
 661                }
 662
 663
 664                // Parse the response status line.
 0665                var response = new HttpResponseMessage() { RequestMessage = request, Content = new HttpConnectionRespons
 666
 0667                while (!ParseStatusLine(response))
 0668                {
 0669                    await FillForHeadersAsync(async).ConfigureAwait(false);
 0670                }
 671
 0672                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStart();
 673
 674                // Multiple 1xx responses handling.
 675                // RFC 7231: A client MUST be able to parse one or more 1xx responses received prior to a final response
 676                // even if the client does not expect one. A user agent MAY ignore unexpected 1xx responses.
 677                // In .NET Core, apart from 100 Continue, and 101 Switching Protocols, we will treat all other 1xx respo
 678                // as unknown, and will discard them.
 0679                while ((uint)(response.StatusCode - 100) <= 199 - 100)
 0680                {
 681                    // If other 1xx responses come before an expected 100 continue, we will wait for the 100 response be
 682                    // sending request body (if any).
 0683                    if (allowExpect100ToContinue != null && response.StatusCode == HttpStatusCode.Continue)
 0684                    {
 0685                        allowExpect100ToContinue.TrySetResult(true);
 0686                        allowExpect100ToContinue = null;
 0687                    }
 0688                    else if (response.StatusCode == HttpStatusCode.SwitchingProtocols)
 0689                    {
 690                        // 101 Upgrade is a final response as it's used to switch protocols with WebSockets handshake.
 691                        // Will return a response object with status 101 and a raw connection stream later.
 692                        // RFC 7230: If a server receives both an Upgrade and an Expect header field with the "100-conti
 693                        // the server MUST send a 100 (Continue) response before sending a 101 (Switching Protocols) res
 694                        // If server doesn't follow RFC, we treat 101 as a final response and stop waiting for 100 conti
 695                        // never sends a 100-continue. The request body will be sent after expect100Timer expires.
 0696                        break;
 697                    }
 698
 699                    // In case read hangs which eventually leads to connection timeout.
 0700                    if (NetEventSource.Log.IsEnabled()) Trace($"Current {response.StatusCode} response is an interim res
 701
 702                    // Discard headers that come with the interim 1xx responses.
 0703                    while (!ParseHeaders(response: null, isFromTrailer: false))
 0704                    {
 0705                        await FillForHeadersAsync(async).ConfigureAwait(false);
 0706                    }
 707
 708                    // Parse the status line for next response.
 0709                    while (!ParseStatusLine(response))
 0710                    {
 0711                        await FillForHeadersAsync(async).ConfigureAwait(false);
 0712                    }
 0713                }
 714
 715                // Parse the response headers.  Logic after this point depends on being able to examine headers in the r
 0716                while (!ParseHeaders(response, isFromTrailer: false))
 0717                {
 0718                    await FillForHeadersAsync(async).ConfigureAwait(false);
 0719                }
 720
 0721                if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop((int)response.StatusCode);
 722
 0723                if (allowExpect100ToContinue != null)
 0724                {
 725                    // If we sent an Expect: 100-continue header, and didn't receive a 100-continue. Handle the final re
 726                    // Note that the developer may have added an Expect: 100-continue header even if there is no Content
 0727                    if ((int)response.StatusCode >= 300 &&
 0728                        request.Content != null &&
 0729                        (request.Content.Headers.ContentLength == null || request.Content.Headers.ContentLength.GetValue
 0730                        !AuthenticationHelper.IsSessionAuthenticationChallenge(response))
 0731                    {
 732                        // For error final status codes, try to avoid sending the payload if its size is unknown or if i
 733                        // If we already sent a header detailing the size of the payload, if we then don't send that pay
 734                        // for it and assume that the next request on the connection is actually this request's payload.
 735                        // to be closed.  However, we may have also lost a race condition with the Expect: 100-continue 
 736                        // we've already started sending the payload (we weren't able to cancel it), then we don't need 
 737                        // We also must not clone connection if we do NTLM or Negotiate authentication.
 0738                        allowExpect100ToContinue.TrySetResult(false);
 739
 0740                        if (!allowExpect100ToContinue.Task.Result) // if Result is true, the timeout already expired and
 0741                        {
 0742                            _connectionClose = true;
 0743                        }
 0744                    }
 745                    else
 0746                    {
 747                        // For any success status codes, for errors when the request content length is known to be small
 748                        // or for session-based authentication challenges, send the payload
 749                        // (if there is one... if there isn't, Content is null and thus allowExpect100ToContinue is also
 0750                        allowExpect100ToContinue.TrySetResult(true);
 0751                    }
 0752                }
 753
 754                // Determine whether we need to force close the connection when the request/response has completed.
 0755                if (response.Headers.ConnectionClose.GetValueOrDefault())
 0756                {
 0757                    _connectionClose = true;
 0758                }
 759
 760                // Now that we've received our final status line, wait for the request content to fully send.
 761                // In most common scenarios, the server won't send back a response until all of the request
 762                // content has been received, so this task should generally already be complete.
 0763                if (sendRequestContentTask != null)
 0764                {
 0765                    Task sendTask = sendRequestContentTask;
 0766                    sendRequestContentTask = null;
 0767                    await sendTask.ConfigureAwait(false);
 0768                }
 769
 770                // Now we are sure that the request was fully sent.
 0771                if (NetEventSource.Log.IsEnabled()) Trace("Request is fully sent.");
 772
 773                // We're about to create the response stream, at which point responsibility for canceling
 774                // the remainder of the response lies with the stream.  Thus we dispose of our registration
 775                // here (if an exception has occurred or does occur while creating/returning the stream,
 776                // we'll still dispose of it in the catch below as part of Dispose'ing the connection).
 0777                cancellationRegistration.Dispose();
 0778                CancellationHelper.ThrowIfCancellationRequested(cancellationToken); // in case cancellation may have dis
 779
 780                // Create the response stream.
 781                Stream responseStream;
 0782                if (request.Method.IsConnect && response.IsSuccessStatusCode)
 0783                {
 784                    // Successful response to CONNECT does not have body.
 785                    // What ever comes next should be opaque.
 0786                    responseStream = new RawConnectionStream(this);
 787
 788                    // Don't put connection back to the pool if we upgraded to tunnel.
 789                    // We cannot use it for normal HTTP requests any more.
 0790                    _connectionClose = true;
 791
 0792                    _pool.InvalidateHttp11Connection(this);
 0793                    _detachedFromPool = true;
 0794                }
 0795                else if (request.Method.IsHead || response.StatusCode is HttpStatusCode.NoContent or HttpStatusCode.NotM
 0796                {
 0797                    responseStream = EmptyReadStream.Instance;
 0798                    CompleteResponse();
 0799                }
 0800                else if (response.StatusCode == HttpStatusCode.SwitchingProtocols)
 0801                {
 0802                    responseStream = new RawConnectionStream(this);
 803
 804                    // Don't put connection back to the pool if we switched protocols.
 805                    // We cannot use it for normal HTTP requests any more.
 0806                    _connectionClose = true;
 807
 0808                    _pool.InvalidateHttp11Connection(this);
 0809                    _detachedFromPool = true;
 0810                }
 0811                else if (response.Headers.TransferEncodingChunked == true)
 0812                {
 0813                    responseStream = new ChunkedEncodingReadStream(this, response);
 0814                }
 0815                else if (response.Content.Headers.ContentLength != null)
 0816                {
 0817                    long contentLength = response.Content.Headers.ContentLength.GetValueOrDefault();
 0818                    if (contentLength <= 0)
 0819                    {
 0820                        responseStream = EmptyReadStream.Instance;
 0821                        CompleteResponse();
 0822                    }
 823                    else
 0824                    {
 0825                        responseStream = new ContentLengthReadStream(this, (ulong)contentLength);
 0826                    }
 0827                }
 828                else
 0829                {
 0830                    responseStream = new ConnectionCloseReadStream(this);
 0831                }
 0832                ((HttpConnectionResponseContent)response.Content).SetStream(responseStream);
 833
 0834                if (NetEventSource.Log.IsEnabled()) Trace($"Received response: {response}");
 835
 836                // Process Set-Cookie headers.
 0837                if (_pool.Settings._useCookies)
 0838                {
 0839                    CookieHelper.ProcessReceivedCookies(response, _pool.Settings._cookieContainer!);
 0840                }
 841
 0842                return response;
 843            }
 0844            catch (Exception error)
 0845            {
 846                // Clean up the cancellation registration in case we're still registered.
 0847                cancellationRegistration.Dispose();
 848
 849                // Make sure to complete the allowExpect100ToContinue task if it exists.
 0850                if (allowExpect100ToContinue is not null && !allowExpect100ToContinue.TrySetResult(false))
 0851                {
 852                    // allowExpect100ToContinue was already signaled and we may have started sending the request body.
 0853                    _canRetry = false;
 0854                }
 855
 0856                if (_readAheadTask != default)
 0857                {
 0858                    Debug.Assert(_readAheadTaskStatus is ReadAheadTask_CompletionReserved or ReadAheadTask_Completed);
 859
 0860                    LogExceptions(_readAheadTask.AsTask());
 0861                }
 862
 0863                if (NetEventSource.Log.IsEnabled()) Trace($"Error sending request: {error}");
 864
 865                // In the rare case where Expect: 100-continue was used and then processing
 866                // of the response headers encountered an error such that we weren't able to
 867                // wait for the sending to complete, it's possible the sending also encountered
 868                // an exception or potentially is still going and will encounter an exception
 869                // (we're about to Dispose for the connection). In such cases, we don't want any
 870                // exception in that sending task to become unobserved and raise alarm bells, so we
 871                // hook up a continuation that will log it.
 0872                if (sendRequestContentTask != null && !sendRequestContentTask.IsCompletedSuccessfully)
 0873                {
 874                    // In case the connection is disposed, it's most probable that
 875                    // expect100Continue timer expired and request content sending failed.
 876                    // We're awaiting the task to propagate the exception in this case.
 0877                    if (_disposed)
 0878                    {
 879                        try
 0880                        {
 0881                            await sendRequestContentTask.ConfigureAwait(false);
 0882                        }
 883                        // Map the exception the same way as we normally do.
 0884                        catch (Exception ex) when (MapSendException(ex, cancellationToken, out Exception mappedEx))
 0885                        {
 0886                            throw mappedEx;
 887                        }
 0888                    }
 0889                    LogExceptions(sendRequestContentTask);
 0890                }
 891
 892                // Now clean up the connection.
 0893                Dispose();
 894
 895                // At this point, we're going to throw an exception; we just need to
 896                // determine which exception to throw.
 0897                if (MapSendException(error, cancellationToken, out Exception mappedException))
 0898                {
 0899                    throw mappedException;
 900                }
 901                // Otherwise, just allow the original exception to propagate.
 0902                throw;
 903            }
 0904        }
 905
 906        private bool MapSendException(Exception exception, CancellationToken cancellationToken, out Exception mappedExce
 0907        {
 0908            if (CancellationHelper.ShouldWrapInOperationCanceledException(exception, cancellationToken))
 0909            {
 910                // Cancellation was requested, so assume that the failure is due to
 911                // the cancellation request. This is a bit unorthodox, as usually we'd
 912                // prioritize a non-OperationCanceledException over a cancellation
 913                // request to avoid losing potentially pertinent information.  But given
 914                // the cancellation design where we tear down the underlying connection upon
 915                // a cancellation request, which can then result in a myriad of different
 916                // exceptions (argument exceptions, object disposed exceptions, socket exceptions,
 917                // etc.), as a middle ground we treat it as cancellation, but still propagate the
 918                // original information as the inner exception, for diagnostic purposes.
 0919                mappedException = CancellationHelper.CreateOperationCanceledException(exception, cancellationToken);
 0920                return true;
 921            }
 922
 0923            if (exception is InvalidOperationException)
 0924            {
 925                // For consistency with other handlers we wrap the exception in an HttpRequestException.
 0926                mappedException = new HttpRequestException(SR.net_http_client_execution_error, exception);
 0927                return true;
 928            }
 929
 0930            if (exception is IOException ioe)
 0931            {
 932                // For consistency with other handlers we wrap the exception in an HttpRequestException.
 933                // If the request is retryable, indicate that on the exception.
 0934                HttpRequestError error = ioe is HttpIOException httpIoe ? httpIoe.HttpRequestError : HttpRequestError.Un
 0935                mappedException = new HttpRequestException(error, SR.net_http_client_execution_error, ioe, _canRetry ? R
 0936                return true;
 937            }
 938
 939            // Otherwise, just allow the original exception to propagate.
 0940            mappedException = exception;
 0941            return false;
 0942        }
 943
 944        private HttpContentWriteStream CreateRequestContentStream(HttpRequestMessage request)
 0945        {
 0946            Debug.Assert(request.Content is not null);
 0947            bool requestTransferEncodingChunked = request.HasHeaders && request.Headers.TransferEncodingChunked == true;
 0948            HttpContentWriteStream requestContentStream = requestTransferEncodingChunked ? (HttpContentWriteStream)
 0949                new ChunkedEncodingWriteStream(this) :
 0950                new ContentLengthWriteStream(this, request.Content.Headers.ContentLength.GetValueOrDefault());
 0951            return requestContentStream;
 0952        }
 953
 954        private CancellationTokenRegistration RegisterCancellation(CancellationToken cancellationToken)
 0955        {
 956            // Cancellation design:
 957            // - We register with the SendAsync CancellationToken for the duration of the SendAsync operation.
 958            // - We register with the Read/Write/CopyToAsync methods on the response stream for each such individual ope
 959            // - The registration disposes of the connection, tearing it down and causing any pending operations to wake
 960            // - Because such a tear down can result in a variety of different exception types, we check for a cancellat
 961            //   request and prioritize that over other exceptions, wrapping the actual exception as an inner of an OCE.
 0962            return cancellationToken.Register(static s =>
 0963            {
 0964                var connection = (HttpConnection)s!;
 0965                if (NetEventSource.Log.IsEnabled()) connection.Trace("Cancellation requested. Disposing of the connectio
 0966                connection.Dispose();
 0967            }, this);
 0968        }
 969
 970        private async ValueTask SendRequestContentAsync(HttpRequestMessage request, HttpContentWriteStream stream, bool 
 0971        {
 0972            Debug.Assert(stream.BytesWritten == 0);
 0973            if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStart();
 974
 975            // Copy all of the data to the server.
 0976            if (async)
 0977            {
 0978                await request.Content!.CopyToAsync(stream, _transportContext, cancellationToken).ConfigureAwait(false);
 0979            }
 980            else
 0981            {
 0982                request.Content!.CopyTo(stream, _transportContext, cancellationToken);
 0983            }
 984
 985            // Finish the content; with a chunked upload, this includes writing the terminating chunk.
 0986            await stream.FinishAsync(async).ConfigureAwait(false);
 987
 988            // Flush any content that might still be buffered.
 0989            await FlushAsync(async).ConfigureAwait(false);
 990
 0991            if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(stream.BytesWritten);
 992
 0993            if (NetEventSource.Log.IsEnabled()) Trace("Finished sending request content.");
 0994        }
 995
 996        private async Task SendRequestContentWithExpect100ContinueAsync(
 997            HttpRequestMessage request, Task<bool> allowExpect100ToContinueTask,
 998            HttpContentWriteStream stream, Timer expect100Timer, bool async, CancellationToken cancellationToken)
 0999        {
 1000            // Wait until we receive a trigger notification that it's ok to continue sending content.
 1001            // This will come either when the timer fires or when we receive a response status line from the server.
 01002            bool sendRequestContent = await allowExpect100ToContinueTask.ConfigureAwait(false);
 1003
 1004            // Clean up the timer; it's no longer needed.
 01005            expect100Timer.Dispose();
 1006
 1007            // Send the content if we're supposed to.  Otherwise, we're done.
 01008            if (sendRequestContent)
 01009            {
 01010                if (NetEventSource.Log.IsEnabled()) Trace($"Sending request content for Expect: 100-continue.");
 1011                try
 01012                {
 01013                    await SendRequestContentAsync(request, stream, async, cancellationToken).ConfigureAwait(false);
 01014                }
 01015                catch
 01016                {
 1017                    // Tear down the connection if called from the timer thread because caller's thread will wait for se
 1018                    // or till HttpClient.Timeout tear the connection itself.
 01019                    Dispose();
 01020                    throw;
 1021                }
 01022            }
 1023            else
 01024            {
 01025                if (NetEventSource.Log.IsEnabled()) Trace($"Canceling request content for Expect: 100-continue.");
 01026            }
 01027        }
 1028
 1029        private bool ParseStatusLine(HttpResponseMessage response)
 01030        {
 01031            Span<byte> buffer = _readBuffer.ActiveSpan;
 1032
 01033            int lineFeedIndex = buffer.IndexOf((byte)'\n');
 01034            if (lineFeedIndex >= 0)
 01035            {
 01036                int bytesConsumed = lineFeedIndex + 1;
 01037                _readBuffer.Discard(bytesConsumed);
 01038                _allowedReadLineBytes -= bytesConsumed;
 1039
 01040                int carriageReturnIndex = lineFeedIndex - 1;
 01041                int length = (uint)carriageReturnIndex < (uint)buffer.Length && buffer[carriageReturnIndex] == '\r'
 01042                    ? carriageReturnIndex
 01043                    : lineFeedIndex;
 1044
 01045                ParseStatusLineCore(buffer.Slice(0, length), response);
 01046                return true;
 1047            }
 1048            else
 01049            {
 01050                if (_allowedReadLineBytes <= buffer.Length)
 01051                {
 01052                    ThrowExceededAllowedReadLineBytes();
 01053                }
 01054                return false;
 1055            }
 01056        }
 1057
 1058        private static void ParseStatusLineCore(Span<byte> line, HttpResponseMessage response)
 01059        {
 1060            // We sent the request version as either 1.0 or 1.1.
 1061            // We expect a response version of the form 1.X, where X is a single digit as per RFC.
 1062
 1063            // Validate the beginning of the status line and set the response version.
 1064            const int MinStatusLineLength = 12; // "HTTP/1.x 123"
 01065            if (line.Length < MinStatusLineLength || line[8] != ' ')
 01066            {
 01067                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 1068            }
 1069
 01070            ulong first8Bytes = BitConverter.ToUInt64(line);
 01071            if (first8Bytes == s_http11Bytes)
 01072            {
 01073                response.SetVersionWithoutValidation(HttpVersion.Version11);
 01074            }
 01075            else if (first8Bytes == s_http10Bytes)
 01076            {
 01077                response.SetVersionWithoutValidation(HttpVersion.Version10);
 01078            }
 1079            else
 01080            {
 01081                byte minorVersion = line[7];
 01082                if (IsDigit(minorVersion) && line.StartsWith("HTTP/1."u8))
 01083                {
 01084                    response.SetVersionWithoutValidation(new Version(1, minorVersion - '0'));
 01085                }
 1086                else
 01087                {
 01088                    throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_respo
 1089                }
 01090            }
 1091
 1092            // Set the status code
 01093            byte status1 = line[9], status2 = line[10], status3 = line[11];
 01094            if (!IsDigit(status1) || !IsDigit(status2) || !IsDigit(status3))
 01095            {
 01096                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 1097            }
 01098            response.SetStatusCodeWithoutValidation((HttpStatusCode)(100 * (status1 - '0') + 10 * (status2 - '0') + (sta
 1099
 1100            // Parse (optional) reason phrase
 01101            if (line.Length == MinStatusLineLength)
 01102            {
 01103                response.SetReasonPhraseWithoutValidation(string.Empty);
 01104            }
 01105            else if (line[MinStatusLineLength] == ' ')
 01106            {
 01107                ReadOnlySpan<byte> reasonBytes = line.Slice(MinStatusLineLength + 1);
 01108                string? knownReasonPhrase = HttpStatusDescription.Get(response.StatusCode);
 01109                if (knownReasonPhrase != null && Ascii.Equals(reasonBytes, knownReasonPhrase))
 01110                {
 01111                    response.SetReasonPhraseWithoutValidation(knownReasonPhrase);
 01112                }
 1113                else
 01114                {
 1115                    try
 01116                    {
 01117                        response.ReasonPhrase = HttpRuleParser.DefaultHttpEncoding.GetString(reasonBytes);
 01118                    }
 01119                    catch (FormatException formatEx)
 01120                    {
 01121                        throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_r
 1122                    }
 01123                }
 01124            }
 1125            else
 01126            {
 01127                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 1128            }
 01129        }
 1130
 1131        private bool ParseHeaders(HttpResponseMessage? response, bool isFromTrailer)
 01132        {
 01133            Span<byte> buffer = _readBuffer.ActiveSpan;
 1134
 01135            (bool finished, int bytesConsumed) = ParseHeadersCore(buffer, response, isFromTrailer);
 1136
 01137            int bytesScanned = finished ? bytesConsumed : buffer.Length;
 01138            if (_allowedReadLineBytes < bytesScanned)
 01139            {
 01140                ThrowExceededAllowedReadLineBytes();
 01141            }
 1142
 01143            _readBuffer.Discard(bytesConsumed);
 01144            _allowedReadLineBytes -= bytesConsumed;
 01145            Debug.Assert(_allowedReadLineBytes >= 0);
 1146
 01147            return finished;
 01148        }
 1149
 1150        private (bool finished, int bytesConsumed) ParseHeadersCore(Span<byte> buffer, HttpResponseMessage? response, bo
 01151        {
 01152            int originalBufferLength = buffer.Length;
 1153
 01154            while (true)
 01155            {
 01156                int colIdx = buffer.IndexOfAny((byte)':', (byte)'\n');
 01157                if (colIdx < 0)
 01158                {
 01159                    return (finished: false, bytesConsumed: originalBufferLength - buffer.Length);
 1160                }
 1161
 01162                if (buffer[colIdx] == '\n')
 01163                {
 01164                    if ((colIdx == 1 && buffer[0] == '\r') || colIdx == 0)
 01165                    {
 01166                        return (finished: true, bytesConsumed: originalBufferLength - buffer.Length + colIdx + 1);
 1167                    }
 1168
 01169                    ThrowForInvalidHeaderLine(buffer, colIdx);
 01170                }
 1171
 01172                int valueStartIdx = colIdx + 1;
 01173                if ((uint)valueStartIdx >= (uint)buffer.Length)
 01174                {
 01175                    return (finished: false, bytesConsumed: originalBufferLength - buffer.Length);
 1176                }
 1177
 1178                // Iterate over the value and handle any line folds (new lines followed by SP/HTAB).
 1179                // valueIterator refers to the remainder of the buffer that we can still scan for new lines.
 01180                Span<byte> valueIterator = buffer.Slice(valueStartIdx);
 1181
 01182                while (true)
 01183                {
 01184                    int lfIdx = valueIterator.IndexOf((byte)'\n');
 01185                    if ((uint)lfIdx >= (uint)valueIterator.Length)
 01186                    {
 01187                        return (finished: false, bytesConsumed: originalBufferLength - buffer.Length);
 1188                    }
 1189
 01190                    int crIdx = lfIdx - 1;
 01191                    int crOrLfIdx = (uint)crIdx < (uint)valueIterator.Length && valueIterator[crIdx] == '\r'
 01192                        ? crIdx
 01193                        : lfIdx;
 1194
 01195                    int spIdx = lfIdx + 1;
 01196                    if ((uint)spIdx >= (uint)valueIterator.Length)
 01197                    {
 01198                        return (finished: false, bytesConsumed: originalBufferLength - buffer.Length);
 1199                    }
 1200
 01201                    if (valueIterator[spIdx] is not (byte)'\t' and not (byte)' ')
 01202                    {
 1203                        // Found the end of the header value.
 1204
 01205                        if (response is not null)
 01206                        {
 01207                            ReadOnlySpan<byte> headerName = buffer.Slice(0, valueStartIdx - 1);
 01208                            ReadOnlySpan<byte> headerValue = buffer.Slice(valueStartIdx, buffer.Length - valueIterator.L
 01209                            AddResponseHeader(headerName, headerValue, response, isFromTrailer);
 01210                        }
 1211
 01212                        buffer = buffer.Slice(buffer.Length - valueIterator.Length + spIdx);
 01213                        break;
 1214                    }
 1215
 1216                    // Found an obs-fold (CRLFHT/CRLFSP).
 1217                    // Replace the CRLF with SPSP and keep looking for the final newline.
 01218                    valueIterator[crOrLfIdx] = (byte)' ';
 01219                    valueIterator[lfIdx] = (byte)' ';
 1220
 01221                    valueIterator = valueIterator.Slice(spIdx + 1);
 01222                }
 01223            }
 1224
 1225            static void ThrowForInvalidHeaderLine(ReadOnlySpan<byte> buffer, int newLineIndex) =>
 01226                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 01227        }
 1228
 1229        private void AddResponseHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value, HttpResponseMessage response, 
 01230        {
 1231            // Skip trailing whitespace and check for empty length.
 01232            while (true)
 01233            {
 01234                int spIdx = name.Length - 1;
 1235
 01236                if ((uint)spIdx < (uint)name.Length)
 01237                {
 01238                    if (name[spIdx] != ' ')
 01239                    {
 1240                        // hot path
 01241                        break;
 1242                    }
 1243
 01244                    name = name.Slice(0, spIdx);
 01245                }
 1246                else
 01247                {
 01248                    ThrowForEmptyHeaderName();
 01249                }
 01250            }
 1251
 1252            // Skip leading OWS for value.
 1253            // hot path: loop body runs only once.
 01254            while (value.Length != 0 && value[0] is (byte)' ' or (byte)'\t')
 01255            {
 01256                value = value.Slice(1);
 01257            }
 1258
 1259            // Skip trailing OWS for value.
 01260            while (true)
 01261            {
 01262                int spIdx = value.Length - 1;
 1263
 01264                if ((uint)spIdx >= (uint)value.Length || !(value[spIdx] is (byte)' ' or (byte)'\t'))
 01265                {
 1266                    // hot path
 01267                    break;
 1268                }
 1269
 01270                value = value.Slice(0, spIdx);
 01271            }
 1272
 01273            if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
 01274            {
 01275                ThrowForInvalidHeaderName(name);
 01276            }
 1277
 01278            Encoding? valueEncoding = _pool.Settings._responseHeaderEncodingSelector?.Invoke(descriptor.Name, _currentRe
 1279
 01280            HttpHeaderType headerType = descriptor.HeaderType;
 1281
 1282            // Request headers returned on the response must be treated as custom headers.
 01283            if ((headerType & HttpHeaderType.Request) != 0)
 01284            {
 01285                descriptor = descriptor.AsCustomHeader();
 01286            }
 1287
 1288            string headerValue;
 1289            HttpHeaders headers;
 1290
 01291            if (isFromTrailer)
 01292            {
 01293                if ((headerType & HttpHeaderType.NonTrailing) != 0)
 01294                {
 1295                    // Disallowed trailer fields.
 1296                    // A recipient MUST ignore fields that are forbidden to be sent in a trailer.
 01297                    return;
 1298                }
 1299
 01300                headerValue = descriptor.GetHeaderValue(value, valueEncoding);
 01301                headers = response.TrailingHeaders;
 01302            }
 01303            else if ((headerType & HttpHeaderType.Content) != 0)
 01304            {
 01305                headerValue = descriptor.GetHeaderValue(value, valueEncoding);
 01306                headers = response.Content!.Headers;
 01307            }
 1308            else
 01309            {
 01310                headerValue = GetResponseHeaderValueWithCaching(descriptor, value, valueEncoding);
 01311                headers = response.Headers;
 1312
 01313                if (descriptor.Equals(KnownHeaders.KeepAlive))
 01314                {
 1315                    // We are intentionally going against RFC to honor the Keep-Alive header even if
 1316                    // we haven't received a Keep-Alive connection token to maximize compat with servers.
 01317                    ProcessKeepAliveHeader(headerValue);
 01318                }
 01319            }
 1320
 01321            bool added = headers.TryAddWithoutValidation(descriptor, headerValue);
 01322            Debug.Assert(added);
 1323
 1324            static void ThrowForEmptyHeaderName() =>
 01325                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 1326
 1327            static void ThrowForInvalidHeaderName(ReadOnlySpan<byte> name) =>
 01328                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 01329        }
 1330
 1331        private void ThrowExceededAllowedReadLineBytes() =>
 01332            throw new HttpRequestException(HttpRequestError.ConfigurationLimitExceeded, SR.Format(SR.net_http_response_h
 1333
 1334        private void ProcessKeepAliveHeader(string keepAlive)
 01335        {
 01336            var parsedValues = new UnvalidatedObjectCollection<NameValueHeaderValue>();
 1337
 01338            if (NameValueHeaderValue.GetNameValueListLength(keepAlive, 0, ',', parsedValues) == keepAlive.Length)
 01339            {
 01340                foreach (NameValueHeaderValue nameValue in parsedValues)
 01341                {
 1342                    // The HTTP/1.1 spec does not define any parameters for the Keep-Alive header, so we are using the d
 01343                    if (string.Equals(nameValue.Name, "timeout", StringComparison.OrdinalIgnoreCase))
 01344                    {
 01345                        if (!string.IsNullOrEmpty(nameValue.Value) &&
 01346                            HeaderUtilities.TryParseInt32(nameValue.Value, out int timeout) &&
 01347                            timeout >= 0)
 01348                        {
 1349                            // Some servers are very strict with closing the connection exactly at the timeout.
 1350                            // Avoid using the connection if it is about to exceed the timeout to avoid resulting reques
 1351                            const int OffsetSeconds = 1;
 1352
 01353                            if (timeout <= OffsetSeconds)
 01354                            {
 01355                                _connectionClose = true;
 01356                            }
 1357                            else
 01358                            {
 01359                                _keepAliveTimeoutSeconds = timeout - OffsetSeconds;
 01360                            }
 01361                        }
 01362                    }
 01363                    else if (string.Equals(nameValue.Name, "max", StringComparison.OrdinalIgnoreCase))
 01364                    {
 01365                        if (nameValue.Value == "0")
 01366                        {
 01367                            _connectionClose = true;
 01368                        }
 01369                    }
 01370                }
 01371            }
 01372        }
 1373
 1374        private void WriteToBuffer(ReadOnlySpan<byte> source)
 01375        {
 01376            Debug.Assert(source.Length <= _writeBuffer.AvailableLength);
 01377            source.CopyTo(_writeBuffer.AvailableSpan);
 01378            _writeBuffer.Commit(source.Length);
 01379        }
 1380
 1381        private void Write(ReadOnlySpan<byte> source)
 01382        {
 01383            int remaining = _writeBuffer.AvailableLength;
 1384
 01385            if (source.Length <= remaining)
 01386            {
 1387                // Fits in current write buffer.  Just copy and return.
 01388                WriteToBuffer(source);
 01389                return;
 1390            }
 1391
 01392            if (_writeBuffer.ActiveLength != 0)
 01393            {
 1394                // Fit what we can in the current write buffer and flush it.
 01395                WriteToBuffer(source.Slice(0, remaining));
 01396                source = source.Slice(remaining);
 01397                Flush();
 01398            }
 1399
 01400            if (source.Length >= _writeBuffer.Capacity)
 01401            {
 1402                // Large write.  No sense buffering this.  Write directly to stream.
 01403                WriteToStream(source);
 01404            }
 1405            else
 01406            {
 1407                // Copy remainder into buffer
 01408                WriteToBuffer(source);
 01409            }
 01410        }
 1411
 1412        private ValueTask WriteAsync(ReadOnlyMemory<byte> source)
 01413        {
 01414            int remaining = _writeBuffer.AvailableLength;
 1415
 01416            if (source.Length <= remaining)
 01417            {
 1418                // Fits in current write buffer.  Just copy and return.
 01419                WriteToBuffer(source.Span);
 01420                return default;
 1421            }
 1422
 01423            if (_writeBuffer.ActiveLength != 0)
 01424            {
 1425                // Fit what we can in the current write buffer and flush it.
 01426                WriteToBuffer(source.Span.Slice(0, remaining));
 01427                source = source.Slice(remaining);
 1428
 01429                ValueTask flushTask = FlushAsync(async: true);
 1430
 01431                if (flushTask.IsCompletedSuccessfully)
 01432                {
 01433                    flushTask.GetAwaiter().GetResult();
 1434
 01435                    if (source.Length <= _writeBuffer.Capacity)
 01436                    {
 01437                        WriteToBuffer(source.Span);
 01438                        return default;
 1439                    }
 1440
 1441                    // Fall-through to WriteToStreamAsync
 01442                }
 1443                else
 01444                {
 01445                    return AwaitFlushAndWriteAsync(flushTask, source);
 1446                }
 01447            }
 1448
 1449            // Large write.  No sense buffering this.  Write directly to stream.
 01450            return WriteToStreamAsync(source, async: true);
 1451
 1452            async ValueTask AwaitFlushAndWriteAsync(ValueTask flushTask, ReadOnlyMemory<byte> source)
 01453            {
 01454                await flushTask.ConfigureAwait(false);
 1455
 01456                if (source.Length <= _writeBuffer.Capacity)
 01457                {
 01458                    WriteToBuffer(source.Span);
 01459                }
 1460                else
 01461                {
 01462                    await WriteToStreamAsync(source, async: true).ConfigureAwait(false);
 01463                }
 01464            }
 01465        }
 1466
 1467        private void WriteWithoutBuffering(ReadOnlySpan<byte> source)
 01468        {
 01469            if (_writeBuffer.ActiveLength != 0)
 01470            {
 01471                if (source.Length <= _writeBuffer.AvailableLength)
 01472                {
 1473                    // There's something already in the write buffer, but the content
 1474                    // we're writing can also fit after it in the write buffer.  Copy
 1475                    // the content to the write buffer and then flush it, so that we
 1476                    // can do a single send rather than two.
 01477                    WriteToBuffer(source);
 01478                    Flush();
 01479                    return;
 1480                }
 1481
 1482                // There's data in the write buffer and the data we're writing doesn't fit after it.
 1483                // Do two writes, one to flush the buffer and then another to write the supplied content.
 01484                Flush();
 01485            }
 1486
 01487            WriteToStream(source);
 01488        }
 1489
 1490        private ValueTask WriteWithoutBufferingAsync(ReadOnlyMemory<byte> source, bool async)
 01491        {
 01492            if (_writeBuffer.ActiveLength == 0)
 01493            {
 1494                // There's nothing in the write buffer we need to flush.
 1495                // Just write the supplied data out to the stream.
 01496                return WriteToStreamAsync(source, async);
 1497            }
 1498
 01499            if (source.Length <= _writeBuffer.AvailableLength)
 01500            {
 1501                // There's something already in the write buffer, but the content
 1502                // we're writing can also fit after it in the write buffer.  Copy
 1503                // the content to the write buffer and then flush it, so that we
 1504                // can do a single send rather than two.
 01505                WriteToBuffer(source.Span);
 01506                return FlushAsync(async);
 1507            }
 1508
 1509            // There's data in the write buffer and the data we're writing doesn't fit after it.
 1510            // Do two writes, one to flush the buffer and then another to write the supplied content.
 01511            return FlushThenWriteWithoutBufferingAsync(source, async);
 01512        }
 1513
 1514        private async ValueTask FlushThenWriteWithoutBufferingAsync(ReadOnlyMemory<byte> source, bool async)
 01515        {
 01516            await FlushAsync(async).ConfigureAwait(false);
 01517            await WriteToStreamAsync(source, async).ConfigureAwait(false);
 01518        }
 1519
 1520        private ValueTask WriteHexInt32Async(int value, bool async)
 01521        {
 1522            // Try to format into our output buffer directly.
 01523            if (value.TryFormat(_writeBuffer.AvailableSpan, out int bytesWritten, "X"))
 01524            {
 01525                _writeBuffer.Commit(bytesWritten);
 01526                return default;
 1527            }
 1528
 1529            // If we don't have enough room, do it the slow way.
 01530            if (async)
 01531            {
 01532                Span<byte> temp = stackalloc byte[8]; // max length of Int32 as hex
 01533                bool formatted = value.TryFormat(temp, out bytesWritten, "X");
 01534                Debug.Assert(formatted);
 01535                return WriteAsync(temp.Slice(0, bytesWritten).ToArray());
 1536            }
 1537            else
 01538            {
 1539                // We should have enough capacity to write any hex-encoded int after flushing the buffer.
 01540                Debug.Assert(_writeBuffer.Capacity >= 8);
 1541
 01542                Flush();
 01543                return WriteHexInt32Async(value, async: false);
 1544            }
 01545        }
 1546
 1547        private void Flush()
 01548        {
 01549            ReadOnlySpan<byte> bytes = _writeBuffer.ActiveSpan;
 01550            if (bytes.Length > 0)
 01551            {
 01552                _writeBuffer.Discard(bytes.Length);
 01553                WriteToStream(bytes);
 01554            }
 01555        }
 1556
 1557        private ValueTask FlushAsync(bool async)
 01558        {
 01559            ReadOnlyMemory<byte> bytes = _writeBuffer.ActiveMemory;
 01560            if (bytes.Length > 0)
 01561            {
 01562                _writeBuffer.Discard(bytes.Length);
 01563                return WriteToStreamAsync(bytes, async);
 1564            }
 01565            return default;
 01566        }
 1567
 1568        private void WriteToStream(ReadOnlySpan<byte> source)
 01569        {
 01570            if (NetEventSource.Log.IsEnabled()) Trace($"Writing {source.Length} bytes.");
 01571            _stream.Write(source);
 01572        }
 1573
 1574        private ValueTask WriteToStreamAsync(ReadOnlyMemory<byte> source, bool async)
 01575        {
 01576            if (NetEventSource.Log.IsEnabled()) Trace($"Writing {source.Length} bytes.");
 1577
 01578            if (async)
 01579            {
 01580                return _stream.WriteAsync(source);
 1581            }
 1582            else
 01583            {
 01584                _stream.Write(source.Span);
 01585                return default;
 1586            }
 01587        }
 1588
 1589        private bool TryReadNextChunkedLine(out ReadOnlySpan<byte> line)
 01590        {
 01591            ReadOnlySpan<byte> buffer = _readBuffer.ActiveReadOnlySpan;
 1592
 01593            int lineFeedIndex = buffer.IndexOf((byte)'\n');
 01594            if (lineFeedIndex < 0)
 01595            {
 01596                if (buffer.Length < MaxChunkBytesAllowed)
 01597                {
 01598                    line = default;
 01599                    return false;
 1600                }
 01601            }
 1602            else
 01603            {
 01604                int bytesConsumed = lineFeedIndex + 1;
 01605                if (bytesConsumed <= MaxChunkBytesAllowed)
 01606                {
 01607                    _readBuffer.Discard(bytesConsumed);
 1608
 01609                    int carriageReturnIndex = lineFeedIndex - 1;
 1610
 01611                    int length = (uint)carriageReturnIndex < (uint)buffer.Length && buffer[carriageReturnIndex] == '\r'
 01612                        ? carriageReturnIndex
 01613                        : lineFeedIndex;
 1614
 01615                    line = buffer.Slice(0, length);
 01616                    return true;
 1617                }
 01618            }
 1619
 01620            throw new HttpRequestException(SR.net_http_chunk_too_large);
 01621        }
 1622
 1623        // Does not throw on EOF. Also assumes there is no buffered data.
 1624        private async ValueTask InitialFillAsync(bool async)
 01625        {
 01626            Debug.Assert(!ReadAheadTaskHasStarted);
 01627            Debug.Assert(_readBuffer.AvailableLength == _readBuffer.Capacity);
 01628            Debug.Assert(_readBuffer.AvailableLength >= InitialReadBufferSize);
 1629
 01630            int bytesRead = async ?
 01631                await _stream.ReadAsync(_readBuffer.AvailableMemory).ConfigureAwait(false) :
 01632                _stream.Read(_readBuffer.AvailableSpan);
 1633
 01634            _readBuffer.Commit(bytesRead);
 1635
 01636            if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
 01637        }
 1638
 1639        // Throws IOException on EOF.  This is only called when we expect more data.
 1640        private async ValueTask FillAsync(bool async)
 01641        {
 01642            Debug.Assert(_readAheadTask == default);
 1643
 01644            _readBuffer.EnsureAvailableSpace(1);
 1645
 01646            int bytesRead = async ?
 01647                await _stream.ReadAsync(_readBuffer.AvailableMemory).ConfigureAwait(false) :
 01648                _stream.Read(_readBuffer.AvailableSpan);
 1649
 01650            _readBuffer.Commit(bytesRead);
 1651
 01652            if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
 01653            if (bytesRead == 0)
 01654            {
 01655                throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof);
 1656            }
 01657        }
 1658
 1659        private ValueTask FillForHeadersAsync(bool async)
 01660        {
 1661            // If the start offset is 0, it means we haven't consumed any data since the last FillAsync.
 1662            // If so, read until we either find the next new line or we hit the MaxResponseHeadersLength limit.
 01663            return _readBuffer.ActiveStartOffset == 0
 01664                ? ReadUntilEndOfHeaderAsync(async)
 01665                : FillAsync(async);
 1666
 1667            // This method guarantees that the next call to ParseHeaders will consume at least one header.
 1668            // This is the slow path, but guarantees O(n) worst-case parsing complexity.
 1669            async ValueTask ReadUntilEndOfHeaderAsync(bool async)
 01670            {
 01671                int searchOffset = _readBuffer.ActiveLength;
 01672                if (searchOffset > 0)
 01673                {
 1674                    // The last character we've buffered could be a new line,
 1675                    // we just haven't checked the byte following it to see if it's a space or tab.
 01676                    searchOffset--;
 01677                }
 1678
 01679                while (true)
 01680                {
 01681                    await FillAsync(async).ConfigureAwait(false);
 01682                    Debug.Assert(_readBuffer.ActiveStartOffset == 0);
 01683                    Debug.Assert(_readBuffer.ActiveLength > searchOffset);
 1684
 1685                    // There's no need to search the whole buffer, only look through the new bytes we just read.
 01686                    if (TryFindEndOfLine(_readBuffer.ActiveReadOnlySpan.Slice(searchOffset), out int offset))
 01687                    {
 01688                        break;
 1689                    }
 1690
 01691                    searchOffset += offset;
 1692
 01693                    int readLength = _readBuffer.ActiveLength;
 01694                    if (searchOffset != readLength)
 01695                    {
 01696                        Debug.Assert(searchOffset == readLength - 1 && _readBuffer.ActiveReadOnlySpan[searchOffset] == '
 01697                        if (readLength <= 2)
 01698                        {
 1699                            // There are no headers - we start off with a new line.
 1700                            // This is reachable from ChunkedEncodingReadStream if the buffers allign just right and the
 01701                            break;
 1702                        }
 01703                    }
 1704
 01705                    if (readLength >= _allowedReadLineBytes)
 01706                    {
 01707                        ThrowExceededAllowedReadLineBytes();
 01708                    }
 01709                }
 1710
 1711                static bool TryFindEndOfLine(ReadOnlySpan<byte> buffer, out int searchOffset)
 01712                {
 01713                    Debug.Assert(buffer.Length > 0);
 1714
 01715                    int originalBufferLength = buffer.Length;
 1716
 01717                    while (true)
 01718                    {
 01719                        int newLineOffset = buffer.IndexOf((byte)'\n');
 01720                        if (newLineOffset < 0)
 01721                        {
 01722                            searchOffset = originalBufferLength;
 01723                            return false;
 1724                        }
 1725
 01726                        int tabOrSpaceIndex = newLineOffset + 1;
 01727                        if (tabOrSpaceIndex == buffer.Length)
 01728                        {
 1729                            // The new line is the last character, read again to make sure it doesn't continue with spac
 01730                            searchOffset = originalBufferLength - 1;
 01731                            return false;
 1732                        }
 1733
 01734                        if (buffer[tabOrSpaceIndex] is not (byte)'\t' and not (byte)' ')
 01735                        {
 01736                            searchOffset = 0;
 01737                            return true;
 1738                        }
 1739
 01740                        buffer = buffer.Slice(tabOrSpaceIndex + 1);
 01741                    }
 01742                }
 01743            }
 01744        }
 1745
 1746        private int ReadFromBuffer(Span<byte> buffer)
 01747        {
 01748            ReadOnlySpan<byte> available = _readBuffer.ActiveSpan;
 01749            int toCopy = Math.Min(available.Length, buffer.Length);
 1750
 01751            available.Slice(0, toCopy).CopyTo(buffer);
 01752            _readBuffer.Discard(toCopy);
 1753
 01754            return toCopy;
 01755        }
 1756
 1757        private int Read(Span<byte> destination)
 01758        {
 1759            // This is called when reading the response body.
 1760
 01761            if (_readBuffer.ActiveLength > 0)
 01762            {
 1763                // We have data in the read buffer.  Return it to the caller.
 01764                return ReadFromBuffer(destination);
 1765            }
 1766
 1767            // No data in read buffer.
 1768            // Do an unbuffered read directly against the underlying stream.
 01769            Debug.Assert(_readAheadTask == default, "Read ahead task should have been consumed as part of the headers.")
 01770            int count = _stream.Read(destination);
 01771            if (NetEventSource.Log.IsEnabled()) Trace($"Received {count} bytes.");
 01772            return count;
 01773        }
 1774
 1775        private ValueTask<int> ReadAsync(Memory<byte> destination)
 01776        {
 1777            // This is called when reading the response body.
 1778
 01779            if (_readBuffer.ActiveLength > 0)
 01780            {
 1781                // We have data in the read buffer.  Return it to the caller.
 01782                return new ValueTask<int>(ReadFromBuffer(destination.Span));
 1783            }
 1784
 1785            // No data in read buffer.
 1786            // Do an unbuffered read directly against the underlying stream.
 01787            Debug.Assert(_readAheadTask == default, "Read ahead task should have been consumed as part of the headers.")
 1788
 01789            return NetEventSource.Log.IsEnabled()
 01790                ? ReadAndLogBytesReadAsync(destination)
 01791                : _stream.ReadAsync(destination);
 1792
 1793            async ValueTask<int> ReadAndLogBytesReadAsync(Memory<byte> destination)
 01794            {
 01795                int count = await _stream.ReadAsync(destination).ConfigureAwait(false);
 01796                if (NetEventSource.Log.IsEnabled()) Trace($"Received {count} bytes.");
 01797                return count;
 01798            }
 01799        }
 1800
 1801        private int ReadBuffered(Span<byte> destination)
 01802        {
 1803            // This is called when reading the response body.
 1804
 01805            if (_readBuffer.ActiveLength == 0)
 01806            {
 1807                // Do a buffered read directly against the underlying stream.
 01808                Debug.Assert(_readAheadTask == default, "Read ahead task should have been consumed as part of the header
 1809
 01810                if (destination.Length == 0)
 01811                {
 01812                    return _stream.Read(Array.Empty<byte>());
 1813                }
 1814
 01815                Debug.Assert(_readBuffer.AvailableLength == _readBuffer.Capacity);
 01816                int bytesRead = _stream.Read(_readBuffer.AvailableSpan);
 01817                _readBuffer.Commit(bytesRead);
 1818
 01819                if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
 01820            }
 1821
 1822            // Hand back as much data as we can fit.
 01823            return ReadFromBuffer(destination);
 01824        }
 1825
 1826        private ValueTask<int> ReadBufferedAsync(Memory<byte> destination)
 01827        {
 1828            // If the caller provided buffer, and thus the amount of data desired to be read,
 1829            // is larger than the internal buffer, there's no point going through the internal
 1830            // buffer, so just do an unbuffered read.
 1831            // Also avoid avoid using the internal buffer if the user requested a zero-byte read to allow
 1832            // underlying streams to efficiently handle such a read (e.g. SslStream defering buffer allocation).
 01833            return destination.Length >= _readBuffer.Capacity || destination.Length == 0 ?
 01834                ReadAsync(destination) :
 01835                ReadBufferedAsyncCore(destination);
 01836        }
 1837
 1838        [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
 1839        private async ValueTask<int> ReadBufferedAsyncCore(Memory<byte> destination)
 01840        {
 1841            // This is called when reading the response body.
 1842
 01843            if (_readBuffer.ActiveLength == 0)
 01844            {
 1845                // Do a buffered read directly against the underlying stream.
 01846                Debug.Assert(_readAheadTask == default, "Read ahead task should have been consumed as part of the header
 1847
 01848                Debug.Assert(_readBuffer.AvailableLength == _readBuffer.Capacity);
 01849                int bytesRead = await _stream.ReadAsync(_readBuffer.AvailableMemory).ConfigureAwait(false);
 01850                _readBuffer.Commit(bytesRead);
 1851
 01852                if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
 01853            }
 1854
 1855            // Hand back as much data as we can fit.
 01856            return ReadFromBuffer(destination.Span);
 01857        }
 1858
 1859        private ValueTask CopyFromBufferAsync(Stream destination, bool async, int count, CancellationToken cancellationT
 01860        {
 01861            Debug.Assert(count <= _readBuffer.ActiveLength);
 1862
 01863            if (NetEventSource.Log.IsEnabled()) Trace($"Copying {count} bytes to stream.");
 1864
 01865            ReadOnlyMemory<byte> source = _readBuffer.ActiveMemory.Slice(0, count);
 01866            _readBuffer.Discard(count);
 1867
 01868            if (async)
 01869            {
 01870                return destination.WriteAsync(source, cancellationToken);
 1871            }
 1872            else
 01873            {
 01874                destination.Write(source.Span);
 01875                return default;
 1876            }
 01877        }
 1878
 1879        private Task CopyToUntilEofAsync(Stream destination, bool async, int bufferSize, CancellationToken cancellationT
 01880        {
 01881            Debug.Assert(destination != null);
 1882
 01883            if (_readBuffer.ActiveLength > 0)
 01884            {
 01885                return CopyToUntilEofWithExistingBufferedDataAsync(destination, async, bufferSize, cancellationToken);
 1886            }
 1887
 01888            if (async)
 01889            {
 01890                return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
 1891            }
 1892
 01893            _stream.CopyTo(destination, bufferSize);
 01894            return Task.CompletedTask;
 01895        }
 1896
 1897        private async Task CopyToUntilEofWithExistingBufferedDataAsync(Stream destination, bool async, int bufferSize, C
 01898        {
 01899            int remaining = _readBuffer.ActiveLength;
 01900            Debug.Assert(remaining > 0);
 1901
 01902            await CopyFromBufferAsync(destination, async, remaining, cancellationToken).ConfigureAwait(false);
 1903
 01904            if (async)
 01905            {
 01906                await _stream.CopyToAsync(destination, bufferSize, cancellationToken).ConfigureAwait(false);
 01907            }
 1908            else
 01909            {
 01910                _stream.CopyTo(destination, bufferSize);
 01911            }
 01912        }
 1913
 1914        // Copy *exactly* [length] bytes into destination; throws on end of stream.
 1915        private async Task CopyToContentLengthAsync(Stream destination, bool async, ulong length, int bufferSize, Cancel
 01916        {
 01917            Debug.Assert(destination != null);
 01918            Debug.Assert(length > 0);
 1919
 1920            // Copy any data left in the connection's buffer to the destination.
 01921            int remaining = _readBuffer.ActiveLength;
 01922            if (remaining > 0)
 01923            {
 01924                if ((ulong)remaining > length)
 01925                {
 01926                    remaining = (int)length;
 01927                }
 01928                await CopyFromBufferAsync(destination, async, remaining, cancellationToken).ConfigureAwait(false);
 1929
 01930                length -= (ulong)remaining;
 01931                if (length == 0)
 01932                {
 01933                    return;
 1934                }
 1935
 01936                Debug.Assert(_readBuffer.ActiveLength == 0, "HttpConnection's buffer should have been empty.");
 01937            }
 1938
 1939            // Repeatedly read into HttpConnection's buffer and write that buffer to the destination
 1940            // stream. If after doing so, we find that we filled the whole connection's buffer (which
 1941            // is sized mainly for HTTP headers rather than large payloads), grow the connection's
 1942            // read buffer to the requested buffer size to use for the remainder of the operation. We
 1943            // use a temporary buffer from the ArrayPool so that the connection doesn't hog large
 1944            // buffers from the pool for extended durations, especially if it's going to sit in the
 1945            // connection pool for a prolonged period.
 01946            byte[]? origReadBuffer = null;
 1947            try
 01948            {
 01949                while (true)
 01950                {
 01951                    await FillAsync(async).ConfigureAwait(false);
 1952
 01953                    remaining = (int)Math.Min((ulong)_readBuffer.ActiveLength, length);
 01954                    await CopyFromBufferAsync(destination, async, remaining, cancellationToken).ConfigureAwait(false);
 1955
 01956                    length -= (ulong)remaining;
 01957                    if (length == 0)
 01958                    {
 01959                        return;
 1960                    }
 1961
 1962                    // If we haven't yet grown the buffer (if we previously grew it, then it's sufficiently large), and
 1963                    // if we filled the read buffer while doing the last read (which is at least one indication that the
 1964                    // data arrival rate is fast enough to warrant a larger buffer), and if the buffer size we'd want is
 1965                    // larger than the one we already have, then grow the connection's read buffer to that size.
 01966                    if (origReadBuffer is null)
 01967                    {
 01968                        int currentCapacity = _readBuffer.Capacity;
 01969                        if (remaining == currentCapacity)
 01970                        {
 01971                            int desiredBufferSize = (int)Math.Min((ulong)bufferSize, length);
 01972                            if (desiredBufferSize > currentCapacity)
 01973                            {
 01974                                origReadBuffer = _readBuffer.DangerousGetUnderlyingBuffer();
 01975                                byte[] pooledBuffer = ArrayPool<byte>.Shared.Rent(desiredBufferSize);
 01976                                _readBuffer = new ArrayBuffer(pooledBuffer);
 01977                            }
 01978                        }
 01979                    }
 01980                }
 1981            }
 1982            finally
 01983            {
 01984                if (origReadBuffer is not null)
 01985                {
 01986                    Debug.Assert(origReadBuffer.Length > 0);
 1987
 1988                    // We don't care how much remaining data there was, just if there was any.
 1989                    // Subsequent code is going to check whether the receive buffer is empty
 1990                    // and then force the connection closed if it's not.
 01991                    bool anyDataAvailable = _readBuffer.ActiveLength > 0;
 1992
 01993                    byte[] pooledBuffer = _readBuffer.DangerousGetUnderlyingBuffer();
 01994                    _readBuffer = new ArrayBuffer(origReadBuffer);
 01995                    ArrayPool<byte>.Shared.Return(pooledBuffer);
 1996
 01997                    if (anyDataAvailable)
 01998                    {
 01999                        _readBuffer.Commit(1);
 02000                    }
 02001                }
 02002            }
 02003        }
 2004
 2005        internal void Acquire()
 02006        {
 02007            Debug.Assert(_currentRequest == null);
 02008            Debug.Assert(!_inUse);
 2009
 02010            _inUse = true;
 02011        }
 2012
 2013        internal void Release()
 02014        {
 02015            Debug.Assert(_inUse);
 2016
 02017            _inUse = false;
 2018
 2019            // If the last request already completed (because the response had no content), return the connection to the
 2020            // Otherwise, it will be returned when the response has been consumed and CompleteResponse below is called.
 02021            if (_currentRequest == null)
 02022            {
 02023                ReturnConnectionToPool();
 02024            }
 02025        }
 2026
 2027        /// <summary>
 2028        /// Detach the connection from the pool, so it is no longer counted against the connection limit.
 2029        /// This is used when we are creating a replacement connection for NT auth challenges.
 2030        /// </summary>
 2031        internal void DetachFromPool()
 02032        {
 02033            Debug.Assert(_inUse);
 2034
 02035            _detachedFromPool = true;
 02036        }
 2037
 2038        private void CompleteResponse()
 02039        {
 02040            Debug.Assert(_currentRequest != null, "Expected the connection to be associated with a request.");
 02041            Debug.Assert(_writeBuffer.ActiveLength == 0, "Everything in write buffer should have been flushed.");
 2042
 2043            // Disassociate the connection from a request.
 02044            _currentRequest = null;
 2045
 2046            // If we have extraneous data in the read buffer, don't reuse the connection;
 2047            // otherwise we'd interpret this as part of the next response. Plus, we may
 2048            // have been using a temporary buffer to read this erroneous data, and thus
 2049            // may not even have it any more.
 02050            if (_readBuffer.ActiveLength > 0)
 02051            {
 02052                if (NetEventSource.Log.IsEnabled())
 02053                {
 02054                    Trace("Unexpected data on connection after response read.");
 02055                }
 2056
 02057                _readBuffer.Discard(_readBuffer.ActiveLength);
 02058                _connectionClose = true;
 02059            }
 2060
 2061            // If the connection is no longer in use (i.e. for NT authentication), then we can
 2062            // return it to the pool now; otherwise, it will be returned by the Release method later.
 2063            // The cancellation logic in HTTP/1.1 response stream reading methods is prone to race conditions
 2064            // where CancellationTokenRegistration callbacks may dispose the connection without the disposal
 2065            // leading to an actual cancellation of the response reading methods by an OperationCanceledException.
 2066            // To guard against these cases, it is necessary to check if the connection is disposed before
 2067            // attempting to return it to the pool.
 02068            if (!_inUse && !_disposed)
 02069            {
 02070                ReturnConnectionToPool();
 02071            }
 02072        }
 2073
 2074        public async ValueTask DrainResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
 02075        {
 02076            Debug.Assert(_inUse);
 2077
 02078            if (_connectionClose)
 02079            {
 02080                throw new HttpRequestException(HttpRequestError.UserAuthenticationError, SR.net_http_authconnectionfailu
 2081            }
 2082
 02083            Debug.Assert(response.Content != null);
 02084            Stream stream = response.Content.ReadAsStream(cancellationToken);
 02085            HttpContentReadStream? responseStream = stream as HttpContentReadStream;
 2086
 02087            Debug.Assert(responseStream != null || stream is EmptyReadStream);
 2088
 02089            if (responseStream != null && responseStream.NeedsDrain)
 02090            {
 02091                Debug.Assert(response.RequestMessage == _currentRequest);
 2092
 02093                if (!await responseStream.DrainAsync(_pool.Settings._maxResponseDrainSize).ConfigureAwait(false) ||
 02094                    _connectionClose)       // Draining may have set this
 02095                {
 02096                    throw new HttpRequestException(HttpRequestError.UserAuthenticationError, SR.net_http_authconnectionf
 2097                }
 02098            }
 2099
 02100            Debug.Assert(_currentRequest == null);
 2101
 02102            response.Dispose();
 02103        }
 2104
 2105        private void ReturnConnectionToPool()
 02106        {
 02107            Debug.Assert(!_disposed, "Connection should not be disposed.");
 02108            Debug.Assert(_currentRequest == null, "Connection should no longer be associated with a request.");
 02109            Debug.Assert(_readAheadTask == default, "Expected a previous initial read to already be consumed.");
 02110            Debug.Assert(_readAheadTaskStatus == ReadAheadTask_NotStarted, "Expected SendAsync to reset the read-ahead t
 02111            Debug.Assert(_readBuffer.ActiveLength == 0, "Unexpected data in connection read buffer.");
 2112
 2113            // If we decided not to reuse the connection (either because the server sent Connection: close,
 2114            // or there was some other problem while processing the request that makes the connection unusable),
 2115            // don't put the connection back in the pool.
 02116            if (_connectionClose)
 02117            {
 02118                if (NetEventSource.Log.IsEnabled())
 02119                {
 02120                    Trace("Connection will not be reused.");
 02121                }
 2122
 2123                // We're not putting the connection back in the pool. Dispose it.
 02124                Dispose();
 02125            }
 2126            else
 02127            {
 02128                Debug.Assert(!_detachedFromPool, "Should not be detached from pool unless _connectionClose is true");
 2129
 2130                // Put connection back in the pool.
 02131                _pool.RecycleHttp11Connection(this);
 02132            }
 02133        }
 2134
 02135        public sealed override string ToString() => $"{nameof(HttpConnection)}({_pool})"; // Description for diagnostic 
 2136
 2137        public sealed override void Trace(string message, [CallerMemberName] string? memberName = null) =>
 02138            NetEventSource.Log.HandlerMessage(
 02139                _pool?.GetHashCode() ?? 0,           // pool ID
 02140                GetHashCode(),                       // connection ID
 02141                _currentRequest?.GetHashCode() ?? 0, // request ID
 02142                memberName,                          // method name
 02143                message);                            // message
 2144    }
 2145}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpContentReadStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace System.Net.Http
 9{
 10    internal sealed partial class HttpConnection
 11    {
 12        internal abstract class HttpContentReadStream : HttpContentStream
 13        {
 14            private bool _disposed;
 15
 016            public HttpContentReadStream(HttpConnection connection) : base(connection)
 017            {
 018            }
 19
 020            public sealed override bool CanRead => !_disposed;
 021            public sealed override bool CanWrite => false;
 22
 023            public sealed override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException(SR.net_http_
 24
 025            public sealed override ValueTask WriteAsync(ReadOnlyMemory<byte> destination, CancellationToken cancellation
 26
 027            public virtual bool NeedsDrain => false;
 28
 029            protected bool IsDisposed => _disposed;
 30
 31            protected bool CanReadFromConnection
 32            {
 33                get
 034                {
 35                    // _connection == null typically means that we have finished reading the response.
 36                    // Cancellation may lead to a state where a disposed _connection is not null.
 037                    HttpConnection? connection = _connection;
 038                    return connection != null && !connection._disposed;
 039                }
 40            }
 41
 42            public virtual ValueTask<bool> DrainAsync(int maxDrainBytes)
 043            {
 044                Debug.Fail($"DrainAsync should not be called for this response stream: {GetType()}");
 45                return new ValueTask<bool>(false);
 46            }
 47
 48            protected override void Dispose(bool disposing)
 049            {
 50                // Only attempt draining if we haven't started draining due to disposal; otherwise
 51                // multiple calls to Dispose (which happens frequently when someone disposes of the
 52                // response stream and response content) will kick off multiple concurrent draining
 53                // operations. Also don't delegate to the base if Dispose has already been called,
 54                // as doing so will end up disposing of the connection before we're done draining.
 055                if (Interlocked.Exchange(ref _disposed, true))
 056                {
 057                    return;
 58                }
 59
 060                if (disposing && NeedsDrain)
 061                {
 62                    // Start the asynchronous drain.
 63                    // It may complete synchronously, in which case the connection will be put back in the pool synchron
 64                    // Skip the call to base.Dispose -- it will be deferred until DrainOnDisposeAsync finishes.
 065                    _ = DrainOnDisposeAsync();
 066                    return;
 67                }
 68
 069                base.Dispose(disposing);
 070            }
 71
 72            private async Task DrainOnDisposeAsync()
 073            {
 074                HttpConnection? connection = _connection;        // Will be null after drain succeeds
 075                Debug.Assert(connection != null);
 76                try
 077                {
 078                    bool drained = await DrainAsync(connection._pool.Settings._maxResponseDrainSize).ConfigureAwait(fals
 79
 080                    if (NetEventSource.Log.IsEnabled())
 081                    {
 082                        connection.Trace(drained ?
 083                            "Connection drain succeeded" :
 084                            $"Connection drain failed when MaxResponseDrainSize={connection._pool.Settings._maxResponseD
 085                    }
 086                }
 087                catch (Exception e)
 088                {
 089                    if (NetEventSource.Log.IsEnabled())
 090                    {
 091                        connection.Trace($"Connection drain failed due to exception: {e}");
 092                    }
 93
 94                    // Eat any exceptions and just Dispose.
 095                }
 96
 097                base.Dispose(true);
 098            }
 99        }
 100    }
 101}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpContentWriteStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.IO;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.Net.Http
 10{
 11    internal sealed partial class HttpConnection : IDisposable
 12    {
 13        private abstract class HttpContentWriteStream : HttpContentStream
 14        {
 015            public long BytesWritten { get; protected set; }
 16
 017            public HttpContentWriteStream(HttpConnection connection) : base(connection) =>
 018                Debug.Assert(connection != null);
 19
 020            public sealed override bool CanRead => false;
 021            public sealed override bool CanWrite => _connection != null;
 22
 23            public sealed override void Flush() =>
 024                _connection?.Flush();
 25
 26            public sealed override Task FlushAsync(CancellationToken ignored)
 027            {
 028                HttpConnection? connection = _connection;
 029                return connection != null ?
 030                    connection.FlushAsync(async: true).AsTask() :
 031                    default!;
 032            }
 33
 034            public sealed override int Read(Span<byte> buffer) => throw new NotSupportedException();
 35
 036            public sealed override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) =>
 37
 038            public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationTo
 39
 40            public abstract Task FinishAsync(bool async);
 41        }
 42    }
 43}

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\RawConnectionStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.IO;
 5using System.Runtime.CompilerServices;
 6using System.Runtime.ExceptionServices;
 7using System.Threading;
 8using System.Threading.Tasks;
 9
 10namespace System.Net.Http
 11{
 12    internal sealed partial class HttpConnection : IDisposable
 13    {
 14        private sealed class RawConnectionStream : HttpContentStream
 15        {
 016            public RawConnectionStream(HttpConnection connection) : base(connection)
 017            {
 018                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this);
 019            }
 20
 021            public sealed override bool CanRead => _connection != null;
 022            public sealed override bool CanWrite => _connection != null;
 23
 24            public override int Read(Span<byte> buffer)
 025            {
 026                HttpConnection? connection = _connection;
 027                if (connection == null)
 028                {
 29                    // Response body fully consumed or the caller didn't ask for any data
 030                    return 0;
 31                }
 32
 033                int bytesRead = connection.ReadBuffered(buffer);
 034                if (bytesRead == 0 && buffer.Length != 0)
 035                {
 36                    // We cannot reuse this connection, so close it.
 037                    _connection = null;
 038                    connection.Dispose();
 039                }
 40
 041                return bytesRead;
 042            }
 43
 44            [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
 45            public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 046            {
 047                CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 48
 049                HttpConnection? connection = _connection;
 050                if (connection == null)
 051                {
 52                    // Response body fully consumed
 053                    return 0;
 54                }
 55
 056                ValueTask<int> readTask = connection.ReadBufferedAsync(buffer);
 57                int bytesRead;
 058                if (readTask.IsCompletedSuccessfully)
 059                {
 060                    bytesRead = readTask.Result;
 061                }
 62                else
 063                {
 064                    CancellationTokenRegistration ctr = connection.RegisterCancellation(cancellationToken);
 65                    try
 066                    {
 067                        bytesRead = await readTask.ConfigureAwait(false);
 068                    }
 069                    catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellat
 070                    {
 071                        throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 72                    }
 73                    finally
 074                    {
 075                        ctr.Dispose();
 076                    }
 077                }
 78
 079                if (bytesRead == 0 && buffer.Length != 0)
 080                {
 81                    // A cancellation request may have caused the EOF.
 082                    CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 83
 84                    // We cannot reuse this connection, so close it.
 085                    _connection = null;
 086                    connection.Dispose();
 087                }
 88
 089                return bytesRead;
 090            }
 91
 92            public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
 093            {
 094                ValidateCopyToArguments(destination, bufferSize);
 95
 096                if (cancellationToken.IsCancellationRequested)
 097                {
 098                    return Task.FromCanceled(cancellationToken);
 99                }
 100
 0101                HttpConnection? connection = _connection;
 0102                if (connection == null)
 0103                {
 104                    // null if response body fully consumed
 0105                    return Task.CompletedTask;
 106                }
 107
 0108                Task copyTask = connection.CopyToUntilEofAsync(destination, async: true, bufferSize, cancellationToken);
 0109                if (copyTask.IsCompletedSuccessfully)
 0110                {
 0111                    Finish(connection);
 0112                    return Task.CompletedTask;
 113                }
 114
 0115                return CompleteCopyToAsync(copyTask, connection, cancellationToken);
 0116            }
 117
 118            private async Task CompleteCopyToAsync(Task copyTask, HttpConnection connection, CancellationToken cancellat
 0119            {
 0120                CancellationTokenRegistration ctr = connection.RegisterCancellation(cancellationToken);
 121                try
 0122                {
 0123                    await copyTask.ConfigureAwait(false);
 0124                }
 0125                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0126                {
 0127                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 128                }
 129                finally
 0130                {
 0131                    ctr.Dispose();
 0132                }
 133
 134                // If cancellation is requested and tears down the connection, it could cause the copy
 135                // to end early but think it ended successfully. So we prioritize cancellation in this
 136                // race condition, and if we find after the copy has completed that cancellation has
 137                // been requested, we assume the copy completed due to cancellation and throw.
 0138                CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
 139
 0140                Finish(connection);
 0141            }
 142
 143            private void Finish(HttpConnection connection)
 0144            {
 145                // We cannot reuse this connection, so close it.
 0146                connection.Dispose();
 0147                _connection = null;
 0148            }
 149
 150            public override void Write(ReadOnlySpan<byte> buffer)
 0151            {
 0152                HttpConnection? connection = _connection;
 0153                if (connection == null)
 0154                {
 0155                    throw new IOException(SR.ObjectDisposed_StreamClosed);
 156                }
 157
 0158                if (buffer.Length != 0)
 0159                {
 0160                    connection.WriteWithoutBuffering(buffer);
 0161                }
 0162            }
 163
 164            public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 0165            {
 0166                if (cancellationToken.IsCancellationRequested)
 0167                {
 0168                    return ValueTask.FromCanceled(cancellationToken);
 169                }
 170
 0171                HttpConnection? connection = _connection;
 0172                if (connection == null)
 0173                {
 0174                    return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.ObjectD
 175                }
 176
 0177                if (buffer.Length == 0)
 0178                {
 0179                    return default;
 180                }
 181
 0182                ValueTask writeTask = connection.WriteWithoutBufferingAsync(buffer, async: true);
 0183                return writeTask.IsCompleted ?
 0184                    writeTask :
 0185                    new ValueTask(WaitWithConnectionCancellationAsync(writeTask, connection, cancellationToken));
 0186            }
 187
 0188            public override void Flush() => _connection?.Flush();
 189
 190            public override Task FlushAsync(CancellationToken cancellationToken)
 0191            {
 0192                if (cancellationToken.IsCancellationRequested)
 0193                {
 0194                    return Task.FromCanceled(cancellationToken);
 195                }
 196
 0197                HttpConnection? connection = _connection;
 0198                if (connection == null)
 0199                {
 0200                    return Task.CompletedTask;
 201                }
 202
 0203                ValueTask flushTask = connection.FlushAsync(async: true);
 0204                return flushTask.IsCompleted ?
 0205                    flushTask.AsTask() :
 0206                    WaitWithConnectionCancellationAsync(flushTask, connection, cancellationToken);
 0207            }
 208
 209            private static async Task WaitWithConnectionCancellationAsync(ValueTask task, HttpConnection connection, Can
 0210            {
 0211                CancellationTokenRegistration ctr = connection.RegisterCancellation(cancellationToken);
 212                try
 0213                {
 0214                    await task.ConfigureAwait(false);
 0215                }
 0216                catch (Exception exc) when (CancellationHelper.ShouldWrapInOperationCanceledException(exc, cancellationT
 0217                {
 0218                    throw CancellationHelper.CreateOperationCanceledException(exc, cancellationToken);
 219                }
 220                finally
 0221                {
 0222                    ctr.Dispose();
 0223                }
 0224            }
 225        }
 226    }
 227}

Methods/Properties

.ctor(System.Net.Http.HttpConnection,System.Net.Http.HttpResponseMessage)
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
ReadAsyncCore()
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
CopyToAsyncCore()
PeekChunkFromConnectionBuffer()
ReadChunksFromConnectionBuffer(System.Span`1<System.Byte>,System.Threading.CancellationTokenRegistration)
ReadChunkFromConnectionBuffer(System.Int32,System.Threading.CancellationTokenRegistration)
ValidateChunkExtension(System.ReadOnlySpan`1<System.Byte>)
NeedsDrain()
DrainAsync()
Fill()
FillAsync()
.cctor()
.ctor(System.Net.Http.HttpConnection)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
WriteChunkAsync()
FinishAsync(System.Boolean)
.ctor(System.Net.Http.HttpConnection)
Read(System.Span`1<System.Byte>)
ReadAsync()
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
CompleteCopyToAsync()
Finish(System.Net.Http.HttpConnection)
.ctor(System.Net.Http.HttpConnection,System.UInt64)
Read(System.Span`1<System.Byte>)
ReadAsync()
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
CompleteCopyToAsync()
Finish()
ReadFromConnectionBuffer(System.Int32)
NeedsDrain()
DrainAsync()
.ctor(System.Net.Http.HttpConnection,System.Int64)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FinishAsync(System.Boolean)
.cctor()
.ctor(System.Net.Http.HttpConnectionPool,System.IO.Stream,System.Net.TransportContext,System.Diagnostics.Activity,System.Net.IPEndPoint)
Finalize()
Dispose()
Dispose(System.Boolean)
ReadAheadTaskHasStarted()
PrepareForReuse(System.Boolean)
TryOwnScavengingTaskCompletion()
TryReturnScavengingTaskCompletionOwnership()
CheckUsabilityOnScavenge()
ReadAheadWithZeroByteReadAsync()
TransitionToCompletedAndTryOwnCompletion()
CheckKeepAliveTimeoutExceeded()
TransportContext()
Kind()
ReadBufferSize()
RemainingBuffer()
ConsumeFromRemainingBuffer(System.Int32)
WriteHeaders(System.Net.Http.HttpRequestMessage)
WriteHost(System.Uri)
WriteHeaderCollection(System.Net.Http.Headers.HttpHeaders,System.String)
WriteCRLF()
WriteBytes(System.ReadOnlySpan`1<System.Byte>)
WriteAsciiString(System.String)
WriteString(System.String,System.Text.Encoding)
ThrowForInvalidCharEncoding()
SendAsync()
MapSendException(System.Exception,System.Threading.CancellationToken,System.Exception&)
CreateRequestContentStream(System.Net.Http.HttpRequestMessage)
RegisterCancellation(System.Threading.CancellationToken)
SendRequestContentAsync()
SendRequestContentWithExpect100ContinueAsync()
ParseStatusLine(System.Net.Http.HttpResponseMessage)
ParseStatusLineCore(System.Span`1<System.Byte>,System.Net.Http.HttpResponseMessage)
ParseHeaders(System.Net.Http.HttpResponseMessage,System.Boolean)
ParseHeadersCore(System.Span`1<System.Byte>,System.Net.Http.HttpResponseMessage,System.Boolean)
ThrowForInvalidHeaderLine(System.ReadOnlySpan`1<System.Byte>,System.Int32)
AddResponseHeader(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>,System.Net.Http.HttpResponseMessage,System.Boolean)
ThrowForEmptyHeaderName()
ThrowForInvalidHeaderName(System.ReadOnlySpan`1<System.Byte>)
ThrowExceededAllowedReadLineBytes()
ProcessKeepAliveHeader(System.String)
WriteToBuffer(System.ReadOnlySpan`1<System.Byte>)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>)
AwaitFlushAndWriteAsync()
WriteWithoutBuffering(System.ReadOnlySpan`1<System.Byte>)
WriteWithoutBufferingAsync(System.ReadOnlyMemory`1<System.Byte>,System.Boolean)
FlushThenWriteWithoutBufferingAsync()
WriteHexInt32Async(System.Int32,System.Boolean)
Flush()
FlushAsync(System.Boolean)
WriteToStream(System.ReadOnlySpan`1<System.Byte>)
WriteToStreamAsync(System.ReadOnlyMemory`1<System.Byte>,System.Boolean)
TryReadNextChunkedLine(System.ReadOnlySpan`1<System.Byte>&)
InitialFillAsync()
FillAsync()
FillForHeadersAsync(System.Boolean)
ReadUntilEndOfHeaderAsync()
TryFindEndOfLine(System.ReadOnlySpan`1<System.Byte>,System.Int32&)
ReadFromBuffer(System.Span`1<System.Byte>)
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>)
ReadAndLogBytesReadAsync()
ReadBuffered(System.Span`1<System.Byte>)
ReadBufferedAsync(System.Memory`1<System.Byte>)
ReadBufferedAsyncCore()
CopyFromBufferAsync(System.IO.Stream,System.Boolean,System.Int32,System.Threading.CancellationToken)
CopyToUntilEofAsync(System.IO.Stream,System.Boolean,System.Int32,System.Threading.CancellationToken)
CopyToUntilEofWithExistingBufferedDataAsync()
CopyToContentLengthAsync()
Acquire()
Release()
DetachFromPool()
CompleteResponse()
DrainResponseAsync()
ReturnConnectionToPool()
ToString()
Trace(System.String,System.String)
.ctor(System.Net.Http.HttpConnection)
CanRead()
CanWrite()
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
NeedsDrain()
IsDisposed()
CanReadFromConnection()
DrainAsync(System.Int32)
Dispose(System.Boolean)
DrainOnDisposeAsync()
BytesWritten()
.ctor(System.Net.Http.HttpConnection)
CanRead()
CanWrite()
Flush()
FlushAsync(System.Threading.CancellationToken)
Read(System.Span`1<System.Byte>)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
.ctor(System.Net.Http.HttpConnection)
CanRead()
CanWrite()
Read(System.Span`1<System.Byte>)
ReadAsync()
CopyToAsync(System.IO.Stream,System.Int32,System.Threading.CancellationToken)
CompleteCopyToAsync()
Finish(System.Net.Http.HttpConnection)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
Flush()
FlushAsync(System.Threading.CancellationToken)
WaitWithConnectionCancellationAsync()