Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: validate arguments of CopyTo #1161

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
}
Loading