mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 440 additions and 264 deletions
@ -0,0 +1,72 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="BufferedReadStream"/> type.
|
||||
|
/// </summary>
|
||||
|
internal static class BufferedReadStreamExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Determines whether the complete read range is contained in the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream containing the data.</param>
|
||||
|
/// <param name="offset">The absolute start of the range.</param>
|
||||
|
/// <param name="length">The number of bytes in the range.</param>
|
||||
|
/// <returns>Whether the range is contained in the stream.</returns>
|
||||
|
public static bool IsReadRangeValid(this BufferedReadStream stream, long offset, ulong length) |
||||
|
{ |
||||
|
// Compare the offset first so subtraction cannot underflow, and avoid
|
||||
|
// adding an untrusted length to the offset where it could wrap around.
|
||||
|
ulong streamLength = (ulong)stream.Length; |
||||
|
return (ulong)offset <= streamLength && length <= streamLength - (ulong)offset; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a buffer length when the complete read fits in both the stream and an integer-sized buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream containing the data.</param>
|
||||
|
/// <param name="length">The declared length in bytes.</param>
|
||||
|
/// <param name="bufferLength">The validated length, or zero when the range is invalid.</param>
|
||||
|
/// <returns>Whether the complete read is valid.</returns>
|
||||
|
public static bool TryGetReadLength(this BufferedReadStream stream, ulong length, out int bufferLength) |
||||
|
{ |
||||
|
if (length > int.MaxValue || !stream.IsReadRangeValid(stream.Position, length)) |
||||
|
{ |
||||
|
bufferLength = 0; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bufferLength = (int)length; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads data from the stream into a slice of the provided buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream.</param>
|
||||
|
/// <param name="buffer">The buffer.</param>
|
||||
|
/// <param name="offset">The offset within the buffer where bytes are read into.</param>
|
||||
|
/// <param name="count">The number of bytes, if available, to read.</param>
|
||||
|
/// <returns>The actual number of bytes read.</returns>
|
||||
|
public static int Read(this BufferedReadStream stream, Span<byte> buffer, int offset, int count) |
||||
|
=> stream.Read(buffer.Slice(offset, count)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Advances the stream by the specified number of bytes. Nonpositive counts are ignored.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream.</param>
|
||||
|
/// <param name="count">The number of bytes to skip.</param>
|
||||
|
public static void Skip(this BufferedReadStream stream, int count) |
||||
|
{ |
||||
|
if (count > 0) |
||||
|
{ |
||||
|
// BufferedReadStream is always seekable; its position setter preserves
|
||||
|
// buffered data when the destination is inside the current buffer.
|
||||
|
stream.Position += count; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Common; |
||||
|
|
||||
|
public class BufferedReadStreamExtensionsTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(0L, 8UL, true)] |
||||
|
[InlineData(8L, 0UL, true)] |
||||
|
[InlineData(7L, 2UL, false)] |
||||
|
[InlineData(9L, 0UL, false)] |
||||
|
[InlineData(-1L, 1UL, false)] |
||||
|
[InlineData(long.MaxValue, ulong.MaxValue, false)] |
||||
|
[InlineData(0L, ulong.MaxValue, false)] |
||||
|
public void IsReadRangeValid_ChecksCompleteExtent(long offset, ulong length, bool expected) |
||||
|
{ |
||||
|
using MemoryStream input = new(new byte[8]); |
||||
|
using BufferedReadStream stream = new(Configuration.Default, input); |
||||
|
|
||||
|
Assert.Equal(expected, stream.IsReadRangeValid(offset, length)); |
||||
|
Assert.Equal(0, stream.Position); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(0UL, true, 0)] |
||||
|
[InlineData(6UL, true, 6)] |
||||
|
[InlineData(7UL, false, 0)] |
||||
|
[InlineData(1073741824UL, false, 0)] |
||||
|
[InlineData(4294967294UL, false, 0)] |
||||
|
[InlineData(4294967296UL, false, 0)] |
||||
|
[InlineData(ulong.MaxValue, false, 0)] |
||||
|
public void TryGetReadLength_ReturnsResultWithoutMovingStream(ulong length, bool expected, int expectedLength) |
||||
|
{ |
||||
|
using MemoryStream input = new(new byte[8]); |
||||
|
using BufferedReadStream stream = new(Configuration.Default, input); |
||||
|
stream.Position = 2; |
||||
|
|
||||
|
Assert.Equal(expected, stream.TryGetReadLength(length, out int bufferLength)); |
||||
|
Assert.Equal(expectedLength, bufferLength); |
||||
|
Assert.Equal(2, stream.Position); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(0)] |
||||
|
[InlineData(-1)] |
||||
|
public void Skip_CountZeroOrLower_PositionNotChanged(int count) |
||||
|
{ |
||||
|
using MemoryStream input = new(new byte[8]); |
||||
|
using BufferedReadStream stream = new(Configuration.Default, input); |
||||
|
stream.Position = 4; |
||||
|
|
||||
|
stream.Skip(count); |
||||
|
|
||||
|
Assert.Equal(4, stream.Position); |
||||
|
Assert.Equal(0, stream.ReadByte()); |
||||
|
} |
||||
|
} |
||||
@ -1,111 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Common; |
|
||||
|
|
||||
public class StreamExtensionsTests |
|
||||
{ |
|
||||
[Theory] |
|
||||
[InlineData(0)] |
|
||||
[InlineData(-1)] |
|
||||
public void Skip_CountZeroOrLower_PositionNotChanged(int count) |
|
||||
{ |
|
||||
using (MemoryStream memStream = new(5)) |
|
||||
{ |
|
||||
memStream.Position = 4; |
|
||||
memStream.Skip(count); |
|
||||
|
|
||||
Assert.Equal(4, memStream.Position); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Skip_SeekableStream_SeekIsCalled() |
|
||||
{ |
|
||||
using (SeekableStream seekableStream = new(4)) |
|
||||
{ |
|
||||
seekableStream.Skip(4); |
|
||||
|
|
||||
Assert.Equal(4, seekableStream.Offset); |
|
||||
Assert.Equal(SeekOrigin.Current, seekableStream.Loc); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Skip_NonSeekableStream_BytesAreRead() |
|
||||
{ |
|
||||
using (NonSeekableStream nonSeekableStream = new()) |
|
||||
{ |
|
||||
nonSeekableStream.Skip(5); |
|
||||
|
|
||||
Assert.Equal(3, nonSeekableStream.Counts.Count); |
|
||||
|
|
||||
Assert.Equal(5, nonSeekableStream.Counts[0]); |
|
||||
Assert.Equal(3, nonSeekableStream.Counts[1]); |
|
||||
Assert.Equal(1, nonSeekableStream.Counts[2]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Skip_EofStream_NoExceptionIsThrown() |
|
||||
{ |
|
||||
using (EofStream eofStream = new(7)) |
|
||||
{ |
|
||||
eofStream.Skip(7); |
|
||||
|
|
||||
Assert.Equal(0, eofStream.Position); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private class SeekableStream : MemoryStream |
|
||||
{ |
|
||||
public long Offset; |
|
||||
public SeekOrigin Loc; |
|
||||
|
|
||||
public SeekableStream(int capacity) |
|
||||
: base(capacity) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override long Seek(long offset, SeekOrigin loc) |
|
||||
{ |
|
||||
this.Offset = offset; |
|
||||
this.Loc = loc; |
|
||||
return base.Seek(offset, loc); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private class NonSeekableStream : MemoryStream |
|
||||
{ |
|
||||
public override bool CanSeek => false; |
|
||||
|
|
||||
public List<int> Counts = new(); |
|
||||
|
|
||||
public NonSeekableStream() |
|
||||
: base(4) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override int Read(byte[] buffer, int offset, int count) |
|
||||
{ |
|
||||
this.Counts.Add(count); |
|
||||
|
|
||||
return Math.Min(2, count); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private class EofStream : MemoryStream |
|
||||
{ |
|
||||
public override bool CanSeek => false; |
|
||||
|
|
||||
public EofStream(int capacity) |
|
||||
: base(capacity) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override int Read(byte[] buffer, int offset, int count) |
|
||||
{ |
|
||||
return 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue