< Summary

Class:Azure.Core.Http.Multipart.MultipartReaderStream
Assembly:Azure.Storage.Blobs.Batch
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs.Batch\src\Shared\MultipartReaderStream.cs
Covered lines:92
Uncovered lines:25
Coverable lines:117
Total lines:345
Line coverage:78.6% (92 of 117)
Covered branches:48
Total branches:66
Branch coverage:72.7% (48 of 66)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
.ctor(...)-80%50%
get_FinalBoundaryFound()-100%100%
get_LengthLimit()-100%100%
get_CanRead()-100%100%
get_CanSeek()-100%100%
get_CanWrite()-0%100%
get_Length()-100%100%
get_Position()-100%100%
set_Position(...)-0%0%
Seek(...)-0%0%
SetLength(...)-0%100%
Write(...)-0%100%
WriteAsync(...)-0%100%
Flush()-0%100%
PositionInnerStream()-66.67%75%
UpdatePosition(...)-83.33%66.67%
Read(...)-95.45%90%
ReadAsync()-95.65%90%
SubMatch(...)-100%100%
CompareBuffers(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs.Batch\src\Shared\MultipartReaderStream.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4// Copied from https://github.com/aspnet/AspNetCore/tree/master/src/Http/WebUtilities/src
 5
 6using System;
 7using System.Buffers;
 8using System.Diagnostics;
 9using System.IO;
 10using System.Threading;
 11using System.Threading.Tasks;
 12
 13#pragma warning disable CA1820  // Use IsNullOrEmpty
 14#pragma warning disable IDE0008 // Use explicit type
 15#pragma warning disable IDE0016 // Simplify null check
 16#pragma warning disable IDE0018 // Inline declaration
 17#pragma warning disable IDE0054 // Use compound assignment
 18#pragma warning disable IDE0059 // Unnecessary assignment
 19
 20namespace Azure.Core.Http.Multipart
 21{
 22    internal sealed class MultipartReaderStream : Stream
 23    {
 24        private readonly MultipartBoundary _boundary;
 25        private readonly BufferedReadStream _innerStream;
 26        private readonly ArrayPool<byte> _bytePool;
 27
 28        private readonly long _innerOffset;
 29        private long _position;
 30        private long _observedLength;
 31        private bool _finished;
 32
 33        /// <summary>
 34        /// Creates a stream that reads until it reaches the given boundary pattern.
 35        /// </summary>
 36        /// <param name="stream">The <see cref="BufferedReadStream"/>.</param>
 37        /// <param name="boundary">The boundary pattern to use.</param>
 38        public MultipartReaderStream(BufferedReadStream stream, MultipartBoundary boundary)
 136039            : this(stream, boundary, ArrayPool<byte>.Shared)
 40        {
 136041        }
 42
 43        /// <summary>
 44        /// Creates a stream that reads until it reaches the given boundary pattern.
 45        /// </summary>
 46        /// <param name="stream">The <see cref="BufferedReadStream"/>.</param>
 47        /// <param name="boundary">The boundary pattern to use.</param>
 48        /// <param name="bytePool">The ArrayPool pool to use for temporary byte arrays.</param>
 136049        public MultipartReaderStream(BufferedReadStream stream, MultipartBoundary boundary, ArrayPool<byte> bytePool)
 50        {
 136051            if (stream == null)
 52            {
 053                throw new ArgumentNullException(nameof(stream));
 54            }
 55
 136056            if (boundary == null)
 57            {
 058                throw new ArgumentNullException(nameof(boundary));
 59            }
 60
 136061            _bytePool = bytePool;
 136062            _innerStream = stream;
 136063            _innerOffset = _innerStream.CanSeek ? _innerStream.Position : 0;
 136064            _boundary = boundary;
 136065        }
 66
 144867        public bool FinalBoundaryFound { get; private set; }
 68
 266069        public long? LengthLimit { get; set; }
 70
 71        public override bool CanRead
 72        {
 127273            get { return true; }
 74        }
 75
 76        public override bool CanSeek
 77        {
 127278            get { return _innerStream.CanSeek; }
 79        }
 80
 81        public override bool CanWrite
 82        {
 083            get { return false; }
 84        }
 85
 86        public override long Length
 87        {
 127288            get { return _observedLength; }
 89        }
 90
 91        public override long Position
 92        {
 127293            get { return _position; }
 94            set
 95            {
 096                if (value < 0)
 97                {
 098                    throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be positive.");
 99                }
 0100                if (value > _observedLength)
 101                {
 0102                    throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be less than length."
 103                }
 0104                _position = value;
 0105                if (_position < _observedLength)
 106                {
 0107                    _finished = false;
 108                }
 0109            }
 110        }
 111
 112        public override long Seek(long offset, SeekOrigin origin)
 113        {
 0114            if (origin == SeekOrigin.Begin)
 115            {
 0116                Position = offset;
 117            }
 0118            else if (origin == SeekOrigin.Current)
 119            {
 0120                Position = Position + offset;
 121            }
 122            else // if (origin == SeekOrigin.End)
 123            {
 0124                Position = Length + offset;
 125            }
 0126            return Position;
 127        }
 128
 129        public override void SetLength(long value)
 130        {
 0131            throw new NotSupportedException();
 132        }
 133
 134        public override void Write(byte[] buffer, int offset, int count)
 135        {
 0136            throw new NotSupportedException();
 137        }
 138
 139        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 140        {
 0141            throw new NotSupportedException();
 142        }
 143
 144        public override void Flush()
 145        {
 146            // Flush is allowed on read-only stream
 0147        }
 148
 149        private void PositionInnerStream()
 150        {
 2660151            if (_innerStream.CanSeek && _innerStream.Position != (_innerOffset + _position))
 152            {
 0153                _innerStream.Position = _innerOffset + _position;
 154            }
 2660155        }
 156
 157        private int UpdatePosition(int read)
 158        {
 1300159            _position += read;
 1300160            if (_observedLength < _position)
 161            {
 1300162                _observedLength = _position;
 1300163                if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
 164                {
 0165                    throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} excee
 166                }
 167            }
 1300168            return read;
 169        }
 170
 171        public override int Read(byte[] buffer, int offset, int count)
 172        {
 1882173            if (_finished)
 174            {
 596175                return 0;
 176            }
 177
 1286178            PositionInnerStream();
 1286179            if (!_innerStream.EnsureBuffered(_boundary.FinalBoundaryLength))
 180            {
 0181                throw new IOException("Unexpected end of Stream, the content may have already been read by another compo
 182            }
 1286183            var bufferedData = _innerStream.BufferedData;
 184
 185            // scan for a boundary match, full or partial.
 186            int read;
 1286187            if (SubMatch(bufferedData, _boundary.BoundaryBytes, out var matchOffset, out var matchCount))
 188            {
 189                // We found a possible match, return any data before it.
 1280190                if (matchOffset > bufferedData.Offset)
 191                {
 644192                    read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
 644193                    return UpdatePosition(read);
 194                }
 195
 636196                var length = _boundary.BoundaryBytes.Length;
 197                Debug.Assert(matchCount == length);
 198
 199                // "The boundary may be followed by zero or more characters of
 200                // linear whitespace. It is then terminated by either another CRLF"
 201                // or -- for the final boundary.
 636202                var boundary = _bytePool.Rent(length);
 636203                read = _innerStream.Read(boundary, 0, length);
 636204                _bytePool.Return(boundary);
 205                Debug.Assert(read == length); // It should have all been buffered
 206
 636207                var remainder = _innerStream.ReadLine(lengthLimit: 100); // Whitespace may exceed the buffer.
 636208                remainder = remainder.Trim();
 636209                if (string.Equals("--", remainder, StringComparison.Ordinal))
 210                {
 44211                    FinalBoundaryFound = true;
 212                }
 213                Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un
 636214                _finished = true;
 636215                return 0;
 216            }
 217
 218            // No possible boundary match within the buffered data, return the data from the buffer.
 6219            read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
 6220            return UpdatePosition(read);
 221        }
 222
 223        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationTo
 224        {
 3242225            if (_finished)
 226            {
 1868227                return 0;
 228            }
 229
 1374230            PositionInnerStream();
 1374231            if (!await _innerStream.EnsureBufferedAsync(_boundary.FinalBoundaryLength, cancellationToken).ConfigureAwait
 232            {
 0233                throw new IOException("Unexpected end of Stream, the content may have already been read by another compo
 234            }
 1374235            var bufferedData = _innerStream.BufferedData;
 236
 237            // scan for a boundary match, full or partial.
 238            int matchOffset;
 239            int matchCount;
 240            int read;
 1374241            if (SubMatch(bufferedData, _boundary.BoundaryBytes, out matchOffset, out matchCount))
 242            {
 243                // We found a possible match, return any data before it.
 1368244                if (matchOffset > bufferedData.Offset)
 245                {
 246                    // Sync, it's already buffered
 644247                    read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
 644248                    return UpdatePosition(read);
 249                }
 250
 724251                var length = _boundary.BoundaryBytes.Length;
 252                Debug.Assert(matchCount == length);
 253
 254                // "The boundary may be followed by zero or more characters of
 255                // linear whitespace. It is then terminated by either another CRLF"
 256                // or -- for the final boundary.
 724257                var boundary = _bytePool.Rent(length);
 724258                read = _innerStream.Read(boundary, 0, length);
 724259                _bytePool.Return(boundary);
 260                Debug.Assert(read == length); // It should have all been buffered
 261
 724262                var remainder = await _innerStream.ReadLineAsync(lengthLimit: 100, cancellationToken: cancellationToken)
 724263                remainder = remainder.Trim();
 724264                if (string.Equals("--", remainder, StringComparison.Ordinal))
 265                {
 44266                    FinalBoundaryFound = true;
 267                }
 268                Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un
 269
 724270                _finished = true;
 724271                return 0;
 272            }
 273
 274            // No possible boundary match within the buffered data, return the data from the buffer.
 6275            read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
 6276            return UpdatePosition(read);
 3242277        }
 278
 279        // Does segment1 contain all of matchBytes, or does it end with the start of matchBytes?
 280        // 1: AAAAABBBBBCCCCC
 281        // 2:      BBBBB
 282        // Or:
 283        // 1: AAAAABBB
 284        // 2:      BBBBB
 285        private bool SubMatch(ArraySegment<byte> segment1, byte[] matchBytes, out int matchOffset, out int matchCount)
 286        {
 287            // clear matchCount to zero
 2660288            matchCount = 0;
 289
 290            // case 1: does segment1 fully contain matchBytes?
 291            {
 2660292                var matchBytesLengthMinusOne = matchBytes.Length - 1;
 2660293                var matchBytesLastByte = matchBytes[matchBytesLengthMinusOne];
 2660294                var segmentEndMinusMatchBytesLength = segment1.Offset + segment1.Count - matchBytes.Length;
 295
 2660296                matchOffset = segment1.Offset;
 10486297                while (matchOffset < segmentEndMinusMatchBytesLength)
 298                {
 10442299                    var lookaheadTailChar = segment1.Array[matchOffset + matchBytesLengthMinusOne];
 10442300                    if (lookaheadTailChar == matchBytesLastByte &&
 10442301                        CompareBuffers(segment1.Array, matchOffset, matchBytes, 0, matchBytesLengthMinusOne) == 0)
 302                    {
 2616303                        matchCount = matchBytes.Length;
 2616304                        return true;
 305                    }
 7826306                    matchOffset += _boundary.GetSkipValue(lookaheadTailChar);
 307                }
 308            }
 309
 310            // case 2: does segment1 end with the start of matchBytes?
 44311            var segmentEnd = segment1.Offset + segment1.Count;
 312
 44313            matchCount = 0;
 2136314            for (; matchOffset < segmentEnd; matchOffset++)
 315            {
 1078316                var countLimit = segmentEnd - matchOffset;
 3564317                for (matchCount = 0; matchCount < matchBytes.Length && matchCount < countLimit; matchCount++)
 318                {
 1750319                    if (matchBytes[matchCount] != segment1.Array[matchOffset + matchCount])
 320                    {
 1046321                        matchCount = 0;
 1046322                        break;
 323                    }
 324                }
 1078325                if (matchCount > 0)
 326                {
 327                    break;
 328                }
 329            }
 44330            return matchCount > 0;
 331        }
 332
 333        private static int CompareBuffers(byte[] buffer1, int offset1, byte[] buffer2, int offset2, int count)
 334        {
 418154335            for (; count-- > 0; offset1++, offset2++)
 336            {
 138578337                if (buffer1[offset1] != buffer2[offset2])
 338                {
 98339                    return buffer1[offset1] - buffer2[offset2];
 340                }
 341            }
 2616342            return 0;
 343        }
 344    }
 345}