Skip to content

Commit

Permalink
fix: validate arguments of CopyTo (#1161)
Browse files Browse the repository at this point in the history
Validate arguments of `CopyTo`
  • Loading branch information
vbreuss authored Nov 8, 2024
1 parent 15320f7 commit 17464e6
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 18 deletions.
90 changes: 72 additions & 18 deletions src/TestableIO.System.IO.Abstractions/FileSystemStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,32 @@ public override IAsyncResult BeginWrite(byte[] buffer,
object? state)
=> _stream.BeginWrite(buffer, offset, count, callback, state);

/// <inheritdoc cref="Stream.Close()" />
public override void Close()
{
base.Close();
_stream.Close();
}

/// <inheritdoc cref="Stream.CopyTo(Stream, int)" />
#if NETSTANDARD2_0 || NET462
public new virtual void CopyTo(Stream destination, int bufferSize)
=> _stream.CopyTo(destination, bufferSize);
public new virtual void CopyTo(Stream destination, int bufferSize)
#else
public override void CopyTo(Stream destination, int bufferSize)
=> _stream.CopyTo(destination, bufferSize);
#endif
{
ValidateCopyToArguments(this, destination, bufferSize);
_stream.CopyTo(destination, bufferSize);
}

/// <inheritdoc cref="Stream.CopyToAsync(Stream, int, CancellationToken)" />
public override Task CopyToAsync(Stream destination,
int bufferSize,
CancellationToken cancellationToken)
=> _stream.CopyToAsync(destination, bufferSize, cancellationToken);
{
ValidateCopyToArguments(this, destination, bufferSize);
return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
}

/// <inheritdoc cref="Stream.EndRead(IAsyncResult)" />
public override int EndRead(IAsyncResult asyncResult)
Expand All @@ -141,9 +153,9 @@ public override int Read(byte[] buffer, int offset, int count)
=> _stream.Read(buffer, offset, count);

#if FEATURE_SPAN
/// <inheritdoc cref="Stream.Read(Span{byte})" />
public override int Read(Span<byte> buffer)
=> _stream.Read(buffer);
/// <inheritdoc cref="Stream.Read(Span{byte})" />
public override int Read(Span<byte> buffer)
=> _stream.Read(buffer);
#endif

/// <inheritdoc cref="Stream.ReadAsync(byte[], int, int, CancellationToken)" />
Expand All @@ -154,10 +166,10 @@ public override Task<int> ReadAsync(byte[] buffer,
=> _stream.ReadAsync(buffer, offset, count, cancellationToken);

#if FEATURE_SPAN
/// <inheritdoc cref="Stream.ReadAsync(Memory{byte}, CancellationToken)" />
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = new())
=> _stream.ReadAsync(buffer, cancellationToken);
/// <inheritdoc cref="Stream.ReadAsync(Memory{byte}, CancellationToken)" />
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = new())
=> _stream.ReadAsync(buffer, cancellationToken);
#endif

/// <inheritdoc cref="Stream.ReadByte()" />
Expand All @@ -181,9 +193,9 @@ public override void Write(byte[] buffer, int offset, int count)
=> _stream.Write(buffer, offset, count);

#if FEATURE_SPAN
/// <inheritdoc cref="Stream.Write(ReadOnlySpan{byte})" />
public override void Write(ReadOnlySpan<byte> buffer)
=> _stream.Write(buffer);
/// <inheritdoc cref="Stream.Write(ReadOnlySpan{byte})" />
public override void Write(ReadOnlySpan<byte> buffer)
=> _stream.Write(buffer);
#endif

/// <inheritdoc cref="Stream.WriteAsync(byte[], int, int, CancellationToken)" />
Expand All @@ -194,10 +206,10 @@ public override Task WriteAsync(byte[] buffer,
=> _stream.WriteAsync(buffer, offset, count, cancellationToken);

#if FEATURE_SPAN
/// <inheritdoc cref="Stream.WriteAsync(ReadOnlyMemory{byte}, CancellationToken)" />
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = new())
=> _stream.WriteAsync(buffer, cancellationToken);
/// <inheritdoc cref="Stream.WriteAsync(ReadOnlyMemory{byte}, CancellationToken)" />
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = new())
=> _stream.WriteAsync(buffer, cancellationToken);
#endif

/// <inheritdoc cref="Stream.WriteByte(byte)" />
Expand All @@ -211,6 +223,15 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}

#if FEATURE_ASYNC_FILE
/// <inheritdoc cref="Stream.DisposeAsync()" />
public override async ValueTask DisposeAsync()
{
await _stream.DisposeAsync();
await base.DisposeAsync();
}
#endif

/// <summary>
/// Allows to cast the internal Stream to a FileStream
/// </summary>
Expand All @@ -220,5 +241,38 @@ public static explicit operator FileStream(FileSystemStream fsStream)
{
return (FileStream) fsStream._stream;
}

private static void ValidateCopyToArguments(Stream source, Stream destination, int bufferSize)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination), "Destination cannot be null.");
}

if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than zero.");
}

if (!destination.CanWrite)
{
if (destination.CanRead)
{
throw new NotSupportedException("Stream does not support writing.");
}

throw new ObjectDisposedException("Cannot access a closed Stream.");
}

if (!source.CanRead)
{
if (source.CanWrite)
{
throw new NotSupportedException("Stream does not support reading.");
}

throw new ObjectDisposedException("Cannot access a closed Stream.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,84 @@ public void MockFileStream_Null_ShouldHaveExpectedProperties()
Assert.That(result.Length, Is.Zero);
Assert.That(result.IsAsync, Is.True);
}

[Test]
[TestCase(0)]
[TestCase(-1)]
public void MockFileStream_WhenBufferSizeIsNotPositive_ShouldThrowArgumentNullException(int bufferSize)
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
fileSystem.File.WriteAllText("bar.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();

Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
await source.CopyToAsync(destination, bufferSize));
}

[Test]
public void MockFileStream_WhenDestinationIsClosed_ShouldThrowObjectDisposedException()
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
using var destination = new MemoryStream();
destination.Close();

Assert.ThrowsAsync<ObjectDisposedException>(async () =>
await source.CopyToAsync(destination));
}

[Test]
public void MockFileStream_WhenDestinationIsNull_ShouldThrowArgumentNullException()
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();

Assert.ThrowsAsync<ArgumentNullException>(async () =>
await source.CopyToAsync(null));
}

[Test]
public void MockFileStream_WhenDestinationIsReadOnly_ShouldThrowNotSupportedException()
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
fileSystem.File.WriteAllText("bar.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenRead();

Assert.ThrowsAsync<NotSupportedException>(async () =>
await source.CopyToAsync(destination));
}

[Test]
public void MockFileStream_WhenSourceIsClosed_ShouldThrowObjectDisposedException()
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
fileSystem.File.WriteAllText("bar.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
source.Close();

Assert.ThrowsAsync<ObjectDisposedException>(async () =>
await source.CopyToAsync(destination));
}

[Test]
public void MockFileStream_WhenSourceIsWriteOnly_ShouldThrowNotSupportedException()
{
var fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("foo.txt", "");
fileSystem.File.WriteAllText("bar.txt", "");
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenWrite();
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();

Assert.ThrowsAsync<NotSupportedException>(async () =>
await source.CopyToAsync(destination));
}
}
}

0 comments on commit 17464e6

Please sign in to comment.