| File: Signing\Archive\ReadOnlyBufferedStream.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; namespace NuGet.Packaging.Signing { public sealed class ReadOnlyBufferedStream : Stream { private const int _defaultBufferSize = 4096; private readonly byte[] _buffer; private readonly bool _leaveOpen; private readonly Lazy<long> _length; private readonly Stream _stream; private long _bufferStartPosition; private int _bufferFillLength; private bool _isDisposed; private long _position; public override bool CanRead => _stream.CanRead; public override bool CanSeek => _stream.CanSeek; public override bool CanTimeout => _stream.CanTimeout; public override bool CanWrite => false; public override long Length => _length.Value; public override long Position { get { ThrowIfDisposed(); return _position; } set { ThrowIfDisposed(); // For stream implementations that support seeking, the Stream contract (per MSDN) is that // "[s]eeking to any location beyond the length of the stream is supported." // So, Position > Length is legal. if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value)); } _position = value; } } public override int ReadTimeout { get { ThrowIfDisposed(); return _stream.ReadTimeout; } set { ThrowIfDisposed(); _stream.ReadTimeout = value; } } public override int WriteTimeout { get { ThrowIfDisposed(); return _stream.WriteTimeout; } set { ThrowIfDisposed(); _stream.WriteTimeout = value; } } public ReadOnlyBufferedStream(Stream stream, bool leaveOpen) : this(stream, leaveOpen, _defaultBufferSize) { } public ReadOnlyBufferedStream(Stream stream, bool leaveOpen, int bufferSize) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (!stream.CanRead) { throw new ArgumentException(Strings.StreamMustBeReadable, nameof(stream)); } if (!stream.CanSeek) { throw new ArgumentException(Strings.StreamMustBeSeekable, nameof(stream)); } if (bufferSize <= 0) { throw new ArgumentOutOfRangeException(nameof(bufferSize)); } _buffer = new byte[bufferSize]; _stream = stream; _leaveOpen = leaveOpen; // Repeated calls to the Length property were found to be costly. _length = new Lazy<long>(() => _stream.Length); } public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { ThrowIfDisposed(); return base.CopyToAsync(destination, bufferSize, cancellationToken); } public override void Flush() { ThrowIfDisposed(); // This is a read-only stream. throw new NotSupportedException(); } public override Task FlushAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); // This is a read-only stream. throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) { ThrowIfDisposed(); if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } if (count > buffer.Length - offset) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, Strings.RangeOutOfBoundsForArray, nameof(offset), nameof(count))); } var bytesRead = 0; var destinationOffset = offset; var bytesWanted = count; while (bytesWanted > 0 && !IsPositionAfterEndOfStream()) { int sourceStart; int bytesToCopy; if (IsPositionInBuffer()) { sourceStart = (int)(Position - _bufferStartPosition); } else { FillBuffer(); sourceStart = 0; } bytesToCopy = (int)Math.Min(_bufferStartPosition + _bufferFillLength - Position, bytesWanted); Debug.Assert(bytesToCopy >= 0); if (bytesToCopy <= 0) { break; } Buffer.BlockCopy(_buffer, sourceStart, buffer, destinationOffset, bytesToCopy); destinationOffset += bytesToCopy; bytesRead += bytesToCopy; bytesWanted -= bytesToCopy; Position += bytesToCopy; } return bytesRead; } public override int ReadByte() { ThrowIfDisposed(); int offset; if (IsPositionInBuffer()) { offset = (int)(Position - _bufferStartPosition); } else if (IsPositionAfterEndOfStream()) { return -1; } else { FillBuffer(); offset = 0; } var value = _buffer[offset]; ++Position; return value; } public override long Seek(long offset, SeekOrigin origin) { ThrowIfDisposed(); long newOffset; switch (origin) { case SeekOrigin.Current: newOffset = Position + offset; break; case SeekOrigin.Begin: newOffset = offset; break; case SeekOrigin.End: newOffset = Length + offset; break; default: throw new ArgumentOutOfRangeException(nameof(origin)); } if (newOffset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } Position = _stream.Seek(newOffset, SeekOrigin.Begin); return Position; } public override void SetLength(long value) { ThrowIfDisposed(); throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { ThrowIfDisposed(); throw new NotSupportedException(); } public override void WriteByte(byte value) { ThrowIfDisposed(); throw new NotSupportedException(); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ThrowIfDisposed(); throw new NotSupportedException(); } protected override void Dispose(bool disposing) { if (disposing) { if (!_isDisposed) { if (!_leaveOpen) { _stream.Dispose(); } _isDisposed = true; } } base.Dispose(disposing); } private void FillBuffer() { _bufferStartPosition = Position; _bufferFillLength = 0; _stream.Position = _bufferStartPosition; var totalBytesRead = 0; var bytesRead = 0; var offset = 0; var count = _buffer.Length; // Read(...) does not guarantee that the requested number of bytes will be read, even if there are ample // bytes in the source. From MSDN: // // An implementation is free to return fewer bytes than requested even if the end of the stream has // not been reached. do { bytesRead = _stream.Read(_buffer, offset, count); offset += bytesRead; count -= bytesRead; totalBytesRead += bytesRead; } while (bytesRead > 0 && count > 0); _bufferFillLength = totalBytesRead; } private bool IsPositionAfterEndOfStream() { return Position >= Length; } private bool IsPositionInBuffer() { return _bufferStartPosition <= Position && Position < _bufferStartPosition + _bufferFillLength; } private void ThrowIfDisposed() { if (_isDisposed) { throw new ObjectDisposedException(nameof(ReadOnlyBufferedStream)); } } } }