Skip to content

Commit 39d3064

Browse files
authored
Flesh out ByteRangeStream methods to make it always limit reads (#213)
* Flesh out `ByteRangeStream` methods to make it always limit reads - #206 - `Seek(...)` was a no-op, `Position` was incorrect if `_lowerbounds != 0`, `ReadAsync(...)` read entire inner `Stream` - rewrite `ByteRangeStreamTest` to cover new `ByteRangeStream` members and hold fewer resources - remove `DelegatingStream.CopyToAsync(...)` override because it copied entire inner `Stream` and was not needed - base implementation invokes `ReadAsync(...)`
1 parent cfda5e5 commit 39d3064

File tree

5 files changed

+582
-78
lines changed

5 files changed

+582
-78
lines changed

src/System.Net.Http.Formatting/Internal/ByteRangeStream.cs

+80-14
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33

44
using System.IO;
55
using System.Net.Http.Headers;
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using System.Web.Http;
79

810
namespace System.Net.Http.Internal
911
{
1012
/// <summary>
11-
/// Stream which only exposes a read-only only range view of an
13+
/// Stream which only exposes a read-only only range view of an
1214
/// inner stream.
1315
/// </summary>
1416
internal class ByteRangeStream : DelegatingStream
1517
{
1618
// The offset stream position at which the range starts.
1719
private readonly long _lowerbounds;
1820

19-
// The total number of bytes within the range.
21+
// The total number of bytes within the range.
2022
private readonly long _totalCount;
2123

2224
// The current number of bytes read into the range
@@ -92,6 +94,23 @@ public override bool CanWrite
9294
get { return false; }
9395
}
9496

97+
public override long Position
98+
{
99+
get
100+
{
101+
return _currentCount;
102+
}
103+
set
104+
{
105+
if (value < 0)
106+
{
107+
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 0L);
108+
}
109+
110+
_currentCount = value;
111+
}
112+
}
113+
95114
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
96115
{
97116
return base.BeginRead(buffer, offset, PrepareStreamForRangeRead(count), callback, state);
@@ -102,16 +121,47 @@ public override int Read(byte[] buffer, int offset, int count)
102121
return base.Read(buffer, offset, PrepareStreamForRangeRead(count));
103122
}
104123

124+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
125+
{
126+
return base.ReadAsync(buffer, offset, PrepareStreamForRangeRead(count), cancellationToken);
127+
}
128+
105129
public override int ReadByte()
106130
{
107131
int effectiveCount = PrepareStreamForRangeRead(1);
108132
if (effectiveCount <= 0)
109133
{
110134
return -1;
111135
}
136+
112137
return base.ReadByte();
113138
}
114139

140+
public override long Seek(long offset, SeekOrigin origin)
141+
{
142+
switch (origin)
143+
{
144+
case SeekOrigin.Begin:
145+
_currentCount = offset;
146+
break;
147+
case SeekOrigin.Current:
148+
_currentCount = _currentCount + offset;
149+
break;
150+
case SeekOrigin.End:
151+
_currentCount = _totalCount + offset;
152+
break;
153+
default:
154+
throw Error.InvalidEnumArgument("origin", (int)origin, typeof(SeekOrigin));
155+
}
156+
157+
if (_currentCount < 0L)
158+
{
159+
throw new IOException(Properties.Resources.ByteRangeStreamInvalidOffset);
160+
}
161+
162+
return _currentCount;
163+
}
164+
115165
public override void SetLength(long value)
116166
{
117167
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
@@ -132,33 +182,49 @@ public override void EndWrite(IAsyncResult asyncResult)
132182
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
133183
}
134184

185+
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
186+
{
187+
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
188+
}
189+
135190
public override void WriteByte(byte value)
136191
{
137192
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
138193
}
139194

140195
/// <summary>
141-
/// Gets the
196+
/// Gets the correct count for the next read operation.
142197
/// </summary>
143198
/// <param name="count">The count requested to be read by the caller.</param>
144199
/// <returns>The remaining bytes to read within the range defined for this stream.</returns>
145200
private int PrepareStreamForRangeRead(int count)
146201
{
147-
long effectiveCount = Math.Min(count, _totalCount - _currentCount);
148-
if (effectiveCount > 0)
202+
// A negative count causes base.Raad* methods to throw an ArgumentOutOfRangeException.
203+
if (count <= 0)
149204
{
150-
// Check if we should update the stream position
151-
long position = InnerStream.Position;
152-
if (_lowerbounds + _currentCount != position)
153-
{
154-
InnerStream.Position = _lowerbounds + _currentCount;
155-
}
205+
return count;
206+
}
156207

157-
// Update current number of bytes read
158-
_currentCount += effectiveCount;
208+
// Reading past the end simply does nothing.
209+
if (_currentCount >= _totalCount)
210+
{
211+
return 0;
159212
}
160213

161-
// Effective count can never be bigger than int
214+
long effectiveCount = Math.Min(count, _totalCount - _currentCount);
215+
216+
// Check if we should update the inner stream's position.
217+
var newPosition = _lowerbounds + _currentCount;
218+
var position = InnerStream.Position;
219+
if (newPosition != position)
220+
{
221+
InnerStream.Position = newPosition;
222+
}
223+
224+
// Update current number of bytes read.
225+
_currentCount += effectiveCount;
226+
227+
// Effective count can never be bigger than int.
162228
return (int)effectiveCount;
163229
}
164230
}

src/System.Net.Http.Formatting/Internal/DelegatingStream.cs

+2-7
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
namespace System.Net.Http.Internal
1010
{
1111
/// <summary>
12-
/// Stream that delegates to inner stream.
12+
/// Stream that delegates to inner stream.
1313
/// This is taken from System.Net.Http
1414
/// </summary>
1515
internal abstract class DelegatingStream : Stream
1616
{
17-
private Stream _innerStream;
17+
private readonly Stream _innerStream;
1818

1919
protected DelegatingStream(Stream innerStream)
2020
{
@@ -119,11 +119,6 @@ public override void Flush()
119119
_innerStream.Flush();
120120
}
121121

122-
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
123-
{
124-
return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
125-
}
126-
127122
public override Task FlushAsync(CancellationToken cancellationToken)
128123
{
129124
return _innerStream.FlushAsync(cancellationToken);

src/System.Net.Http.Formatting/Properties/Resources.Designer.cs

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/System.Net.Http.Formatting/Properties/Resources.resx

+3
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,7 @@
339339
<data name="RemoteStreamInfoCannotBeNull" xml:space="preserve">
340340
<value>The '{0}' method in '{1}' returned null. It must return a RemoteStreamResult instance containing a writable stream and a valid URL.</value>
341341
</data>
342+
<data name="ByteRangeStreamInvalidOffset" xml:space="preserve">
343+
<value>An attempt was made to move the position before the beginning of the stream.</value>
344+
</data>
342345
</root>

0 commit comments

Comments
 (0)