// 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.IO;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace System.Net.Http.Internal
{
///
/// Stream which only exposes a read-only only range view of an
/// inner stream.
///
internal class ByteRangeStream : DelegatingStream
{
// The offset stream position at which the range starts.
private readonly long _lowerbounds;
// The total number of bytes within the range.
private readonly long _totalCount;
// The current number of bytes read into the range
private long _currentCount;
public ByteRangeStream(Stream innerStream, RangeItemHeaderValue range)
: base(innerStream)
{
if (range == null)
{
throw Error.ArgumentNull("range");
}
if (!innerStream.CanSeek)
{
throw Error.Argument("innerStream", Properties.Resources.ByteRangeStreamNotSeekable, typeof(ByteRangeStream).Name);
}
if (innerStream.Length < 1)
{
throw Error.ArgumentOutOfRange("innerStream", innerStream.Length, Properties.Resources.ByteRangeStreamEmpty, typeof(ByteRangeStream).Name);
}
if (range.From.HasValue && range.From.Value > innerStream.Length)
{
throw Error.ArgumentOutOfRange("range", range.From, Properties.Resources.ByteRangeStreamInvalidFrom, innerStream.Length);
}
// Ranges are inclusive so 0-9 means the first 10 bytes
long maxLength = innerStream.Length - 1;
long upperbounds;
if (range.To.HasValue)
{
if (range.From.HasValue)
{
// e.g bytes=0-499 (the first 500 bytes offsets 0-499)
upperbounds = Math.Min(range.To.Value, maxLength);
_lowerbounds = range.From.Value;
}
else
{
// e.g bytes=-500 (the final 500 bytes)
upperbounds = maxLength;
_lowerbounds = Math.Max(innerStream.Length - range.To.Value, 0);
}
}
else
{
if (range.From.HasValue)
{
// e.g bytes=500- (from byte offset 500 and up)
upperbounds = maxLength;
_lowerbounds = range.From.Value;
}
else
{
// e.g. bytes=- (invalid so will never get here)
upperbounds = maxLength;
_lowerbounds = 0;
}
}
_totalCount = upperbounds - _lowerbounds + 1;
ContentRange = new ContentRangeHeaderValue(_lowerbounds, upperbounds, innerStream.Length);
}
public ContentRangeHeaderValue ContentRange { get; private set; }
public override long Length
{
get { return _totalCount; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Position
{
get
{
return _currentCount;
}
set
{
if (value < 0)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 0L);
}
_currentCount = value;
}
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return base.BeginRead(buffer, offset, PrepareStreamForRangeRead(count), callback, state);
}
public override int Read(byte[] buffer, int offset, int count)
{
return base.Read(buffer, offset, PrepareStreamForRangeRead(count));
}
public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return base.ReadAsync(buffer, offset, PrepareStreamForRangeRead(count), cancellationToken);
}
public override int ReadByte()
{
int effectiveCount = PrepareStreamForRangeRead(1);
if (effectiveCount <= 0)
{
return -1;
}
return base.ReadByte();
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
_currentCount = offset;
break;
case SeekOrigin.Current:
_currentCount = _currentCount + offset;
break;
case SeekOrigin.End:
_currentCount = _totalCount + offset;
break;
default:
throw Error.InvalidEnumArgument("origin", (int)origin, typeof(SeekOrigin));
}
if (_currentCount < 0L)
{
throw new IOException(Properties.Resources.ByteRangeStreamInvalidOffset);
}
return _currentCount;
}
public override void SetLength(long value)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
public override void Write(byte[] buffer, int offset, int count)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
public override void EndWrite(IAsyncResult asyncResult)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
public override void WriteByte(byte value)
{
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
}
///
/// Gets the correct count for the next read operation.
///
/// The count requested to be read by the caller.
/// The remaining bytes to read within the range defined for this stream.
private int PrepareStreamForRangeRead(int count)
{
// A negative count causes base.Raad* methods to throw an ArgumentOutOfRangeException.
if (count <= 0)
{
return count;
}
// Reading past the end simply does nothing.
if (_currentCount >= _totalCount)
{
return 0;
}
long effectiveCount = Math.Min(count, _totalCount - _currentCount);
// Check if we should update the inner stream's position.
var newPosition = _lowerbounds + _currentCount;
var position = InnerStream.Position;
if (newPosition != position)
{
InnerStream.Position = newPosition;
}
// Update current number of bytes read.
_currentCount += effectiveCount;
// Effective count can never be bigger than int.
return (int)effectiveCount;
}
}
}