/******************************************************************************* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"). You may not use * this file except in compliance with the License. A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Amazon.Runtime; #if AWS_ASYNC_API using System.Threading; using System.Threading.Tasks; #endif namespace Amazon.Runtime.Internal.Util { /// /// A stream which caches the contents of the underlying stream as it reads it. /// public class CachingWrapperStream : WrapperStream { private readonly int? _cacheLimit; private int _cachedBytes = 0; /// /// All the bytes read by the stream. /// public List AllReadBytes { get; private set; } /// /// All the bytes read by the stream constrained with _cacheLimit /// public List LoggableReadBytes { get { var limit = _cacheLimit ?? AWSConfigs.LoggingConfig.LogResponsesSizeLimit; return AllReadBytes.Take(limit).ToList(); } } /// /// Initializes the CachingWrapperStream with a base stream. /// /// The stream to be wrapped. /// Maximum number of bytes to be cached. public CachingWrapperStream(Stream baseStream, int? cacheLimit = null) : base(baseStream) { _cacheLimit = cacheLimit; this.AllReadBytes = new List(); } /// /// Reads a sequence of bytes from the current stream and advances the position /// within the stream by the number of bytes read. /// /// /// An array of bytes. When this method returns, the buffer contains the specified /// byte array with the values between offset and (offset + count - 1) replaced /// by the bytes read from the current source. /// /// /// The zero-based byte offset in buffer at which to begin storing the data read /// from the current stream. /// /// /// The maximum number of bytes to be read from the current stream. /// /// /// The total number of bytes read into the buffer. This can be less than the /// number of bytes requested if that many bytes are not currently available, /// or zero (0) if the end of the stream has been reached. /// public override int Read(byte[] buffer, int offset, int count) { var numberOfBytesRead = base.Read(buffer, offset, count); UpdateCacheAfterReading(buffer, offset, numberOfBytesRead); return numberOfBytesRead; } #if AWS_ASYNC_API public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { var numberOfBytesRead = await base.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); UpdateCacheAfterReading(buffer, offset, numberOfBytesRead); return numberOfBytesRead; } #endif private void UpdateCacheAfterReading(byte[] buffer, int offset, int numberOfBytesRead) { // Limit the cached bytes to _cacheLimit if (_cacheLimit.HasValue) { if (_cachedBytes < _cacheLimit.Value) { int remainingBytes = _cacheLimit.Value - _cachedBytes; int bytesToCache = Math.Min(numberOfBytesRead, remainingBytes); var readBytes = new byte[bytesToCache]; System.Array.Copy(buffer, offset, readBytes, 0, bytesToCache); AllReadBytes.AddRange(readBytes); _cachedBytes += bytesToCache; } } else { var readBytes = new byte[numberOfBytesRead]; System.Array.Copy(buffer, offset, readBytes, 0, numberOfBytesRead); AllReadBytes.AddRange(readBytes); } } /// /// Gets a value indicating whether the current stream supports seeking. /// CachingWrapperStream does not support seeking, this will always be false. /// public override bool CanSeek { get { // Restrict random access. return false; } } /// /// Gets or sets the position within the current stream. /// CachingWrapperStream does not support seeking, attempting to set Position /// will throw NotSupportedException. /// public override long Position { get { throw new NotSupportedException("CachingWrapperStream does not support seeking"); } set { // Restrict random access, as this will break hashing. throw new NotSupportedException("CachingWrapperStream does not support seeking"); } } /// /// Sets the position within the current stream. /// CachingWrapperStream does not support seeking, attempting to call Seek /// will throw NotSupportedException. /// /// A byte offset relative to the origin parameter. /// /// A value of type System.IO.SeekOrigin indicating the reference point used /// to obtain the new position. /// The new position within the current stream. public override long Seek(long offset, SeekOrigin origin) { // Restrict random access. throw new NotSupportedException("CachingWrapperStream does not support seeking"); } } }