diff --git a/src/Microsoft.Net.Http.Client/BufferedReadStream.cs b/src/Microsoft.Net.Http.Client/BufferedReadStream.cs index 144c7c2c..64357bb8 100644 --- a/src/Microsoft.Net.Http.Client/BufferedReadStream.cs +++ b/src/Microsoft.Net.Http.Client/BufferedReadStream.cs @@ -16,6 +16,8 @@ internal sealed class BufferedReadStream : WriteClosableStream, IPeekableStream private int _bufferCount; + private MemoryStream _readLineBuffer; + public BufferedReadStream(Stream inner, Socket socket, ILogger logger) : this(inner, socket, 8192, logger) { @@ -63,6 +65,8 @@ protected override void Dispose(bool disposing) ArrayPool.Shared.Return(_buffer); } + _readLineBuffer?.Dispose(); + _inner.Dispose(); } @@ -144,15 +148,21 @@ public bool Peek(byte[] buffer, uint toPeek, out uint peeked, out uint available public async Task ReadLineAsync(CancellationToken cancellationToken) { - var line = new StringBuilder(32); - - var crIndex = -1; + if (_readLineBuffer == null) + { + _readLineBuffer = new MemoryStream(); + } + else + { + _readLineBuffer.SetLength(0); + } - var lfIndex = -1; + const byte cr = (byte)'\r'; + const byte lf = (byte)'\n'; - bool crlfFound; + bool crFound = false; - do + while (true) { if (_bufferCount == 0) { @@ -160,29 +170,65 @@ public async Task ReadLineAsync(CancellationToken cancellationToken) _bufferCount = await _inner.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken) .ConfigureAwait(false); - } - var c = (char)_buffer[_bufferOffset]; - line.Append(c); + if (_bufferCount == 0) + { + if (crFound) + { + _readLineBuffer.WriteByte(cr); + } - _bufferOffset++; - _bufferCount--; + if (_readLineBuffer.Length == 0) + { + return null; + } - switch (c) + break; // return what is left in _readLineBuffer + } + } + + if (crFound) { - case '\r': - crIndex = line.Length; - break; - case '\n': - lfIndex = line.Length; + if (_buffer[_bufferOffset] == lf) + { + _bufferOffset++; + _bufferCount--; break; + } + crFound = false; + _readLineBuffer.WriteByte(cr); } - crlfFound = crIndex + 1 == lfIndex; + var crIndex = _buffer.AsSpan(_bufferOffset, _bufferCount).IndexOf(cr); + if (crIndex != -1) + { + _readLineBuffer.Write(_buffer, _bufferOffset, crIndex); + _bufferOffset += crIndex + 1; + _bufferCount -= crIndex + 1; + + if (_bufferCount > 0) + { + if (_buffer[_bufferOffset] == lf) + { + _bufferOffset++; + _bufferCount--; + break; + } + _readLineBuffer.WriteByte(cr); + } + else + { + crFound = true; + } + } + else + { + _readLineBuffer.Write(_buffer, _bufferOffset, _bufferCount); + _bufferCount = 0; + } } - while (!crlfFound); - return line.ToString(0, line.Length - 2); + return Encoding.ASCII.GetString(_readLineBuffer.GetBuffer(), 0, (int)_readLineBuffer.Length); } private int ReadBuffer(byte[] buffer, int offset, int count) @@ -205,7 +251,7 @@ private int PeekBuffer(byte[] buffer, uint toPeek, out uint peeked, out uint ava { int toCopy = Math.Min(_bufferCount, (int)toPeek); Buffer.BlockCopy(_buffer, _bufferOffset, buffer, 0, toCopy); - peeked = (uint) toCopy; + peeked = (uint)toCopy; available = (uint)_bufferCount; remaining = available - peeked; return toCopy; diff --git a/src/Microsoft.Net.Http.Client/ChunkedReadStream.cs b/src/Microsoft.Net.Http.Client/ChunkedReadStream.cs index f127a79b..e4e26346 100644 --- a/src/Microsoft.Net.Http.Client/ChunkedReadStream.cs +++ b/src/Microsoft.Net.Http.Client/ChunkedReadStream.cs @@ -92,7 +92,12 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, var emptyLine = await _inner.ReadLineAsync(cancellationToken) .ConfigureAwait(false); - if (!string.IsNullOrEmpty(emptyLine)) + if (emptyLine == null) + { + throw new EndOfStreamException(); + } + + if (emptyLine.Length > 0) { throw new IOException($"Expected an empty line, but received: '{emptyLine}'."); }