// 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.Diagnostics.Contracts; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; namespace System.Net.Http.Internal { /// /// This implements a read-only, forward-only stream around another readable stream, to ensure /// that there is an appropriate encoding preamble in the stream. /// internal class ReadOnlyStreamWithEncodingPreamble : Stream { private static Task _cancelledTask = GetCancelledTask(); private Stream _innerStream; private ArraySegment _remainingBytes; public ReadOnlyStreamWithEncodingPreamble(Stream innerStream, Encoding encoding) { Contract.Assert(innerStream != null); Contract.Assert(innerStream.CanRead); Contract.Assert(encoding != null); _innerStream = innerStream; // Determine whether we even have a preamble to be concerned about byte[] preamble = encoding.GetPreamble(); int preambleLength = preamble.Length; if (preambleLength <= 0) { return; } // Create a double sized buffer, and read enough bytes from the stream to know // whether we have a preamble present already or not. int finalBufferLength = preambleLength * 2; byte[] finalBuffer = new byte[finalBufferLength]; int finalCount = preambleLength; preamble.CopyTo(finalBuffer, 0); // Read the first bytes of the stream and see if they already contain a preamble for (; finalCount < finalBufferLength; finalCount++) { int b = innerStream.ReadByte(); if (b == -1) { break; } finalBuffer[finalCount] = (byte)b; } // Did we read enough bytes to do the comparison? if (finalCount == finalBufferLength) { bool foundPreamble = true; for (int idx = 0; idx < preambleLength; idx++) { if (finalBuffer[idx] != finalBuffer[idx + preambleLength]) { foundPreamble = false; break; } } // If we found the preamble, then just exclude it from the data that we return if (foundPreamble) { finalCount = preambleLength; } } _remainingBytes = new ArraySegment(finalBuffer, 0, finalCount); } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return false; } } public override long Length { get { throw new NotImplementedException(); } } public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void Flush() { throw new NotImplementedException(); } private static Task GetCancelledTask() { var tcs = new TaskCompletionSource(); tcs.SetCanceled(); return tcs.Task; } public override int Read(byte[] buffer, int offset, int count) { byte[] remainingArray = _remainingBytes.Array; if (remainingArray == null) { return _innerStream.Read(buffer, offset, count); } int remainingCount = _remainingBytes.Count; int remainingOffset = _remainingBytes.Offset; int result = Math.Min(count, remainingCount); for (int idx = 0; idx < result; ++idx) { buffer[offset + idx] = remainingArray[remainingOffset + idx]; } if (result == remainingCount) { _remainingBytes = default(ArraySegment); } else { _remainingBytes = new ArraySegment(remainingArray, remainingOffset + result, remainingCount - result); } return result; } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (_remainingBytes.Array == null) { return _innerStream.ReadAsync(buffer, offset, count, cancellationToken); } if (cancellationToken.IsCancellationRequested) { return _cancelledTask; } return Task.FromResult(Read(buffer, offset, count)); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } } }