Exemplo n.º 1
0
        public async override ValueTask <ReadResult> ReadAsync(CancellationToken cancellationToken = default)
        {
            // ReadAsync needs to handle some common situations:
            // 1. Base64 requires are least 4 bytes to decode content. If less than 4 bytes are returned
            //    from the inner reader then repeatedly call the inner reader until 4 bytes are available.
            // 2. It is possible that ReadAsync is called many times without consuming the data. We don't
            //    want to decode the same base64 content over and over. ReadAsync only decodes new content
            //    and appends it to a sequence.

            var innerResult = await _inner.ReadAsync(cancellationToken);

            if (innerResult.Buffer.IsEmpty)
            {
                _currentDecodedBuffer = innerResult.Buffer;
                _currentInnerBuffer   = innerResult.Buffer;
                return(innerResult);
            }

            // Minimum valid base64 length is 4. Read until we have at least that much content
            while (innerResult.Buffer.Length - _currentInnerBufferRead < 4)
            {
                if (innerResult.IsCompleted)
                {
                    // If the reader completes with less than 4 bytes then the base64 isn't valid..
                    throw new InvalidOperationException("Unexpected end of data when reading base64 content.");
                }

                if (innerResult.IsCanceled)
                {
                    // Cancelled before we have enough data to decode. Return a cancelled result with no data.
                    _currentDecodedBuffer = ReadOnlySequence <byte> .Empty;
                    _currentInnerBuffer   = innerResult.Buffer;
                    return(new ReadResult(
                               ReadOnlySequence <byte> .Empty,
                               innerResult.IsCanceled,
                               innerResult.IsCompleted));
                }

                // Attempt to get more data
                _inner.AdvanceTo(innerResult.Buffer.Start, innerResult.Buffer.End);
                innerResult = await _inner.ReadAsync(cancellationToken);
            }

            // Limit result to complete base64 segments (multiples of 4)
            var newResultLength      = innerResult.Buffer.Length - _currentInnerBufferRead;
            var newResultValidLength = (newResultLength / 4) * 4;

            var buffer = innerResult.Buffer.Slice(_currentInnerBufferRead, newResultValidLength);

            // The content can contain multiple fragments of base64 content
            // Check for padding, and limit returned data to one fragment at a time
            var paddingIndex = PositionOf(buffer, (byte)'=');

            if (paddingIndex != null)
            {
                buffer = buffer.Slice(0, ((paddingIndex.Value / 4) + 1) * 4);
            }

            // Copy the buffer data to a new array.
            // Need a copy that we own because it will be decoded in place.
            var decodedBuffer = buffer.ToArray();

            var status = Base64.DecodeFromUtf8InPlace(decodedBuffer, out var bytesWritten);

            if (status == OperationStatus.Done || status == OperationStatus.NeedMoreData)
            {
                _currentInnerBuffer = innerResult.Buffer.Slice(0, _currentInnerBufferRead + decodedBuffer.Length);

                _currentInnerBufferRead = _currentInnerBuffer.Length;

                // Update decoded buffer. If there have been multiple reads with the same content then
                // newly decoded content will be appended to the sequence.
                if (_currentDecodedBuffer.IsEmpty)
                {
                    // Avoid creating segments for single segment sequence.
                    _currentDecodedBuffer = new ReadOnlySequence <byte>(decodedBuffer, 0, bytesWritten);
                }
                else if (_currentDecodedBuffer.IsSingleSegment)
                {
                    var start = new MemorySegment <byte>(_currentDecodedBuffer.First);

                    // Append new content to end.
                    var end = start.Append(decodedBuffer.AsMemory(0, bytesWritten));

                    _currentDecodedBuffer = new ReadOnlySequence <byte>(start, 0, end, end.Memory.Length);
                }
                else
                {
                    var start = (MemorySegment <byte>)_currentDecodedBuffer.Start.GetObject() !;
                    var end   = (MemorySegment <byte>)_currentDecodedBuffer.End.GetObject() !;

                    // Append new content to end.
                    end = end.Append(decodedBuffer.AsMemory(0, bytesWritten));

                    _currentDecodedBuffer = new ReadOnlySequence <byte>(start, 0, end, end.Memory.Length);
                }

                return(new ReadResult(
                           _currentDecodedBuffer,
                           innerResult.IsCanceled,
                           innerResult.IsCompleted));
            }

            throw new InvalidOperationException("Unexpected status: " + status);
        }