From 32dca9ebe24fa053e5b8f6716ee9166898378a22 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 8 Nov 2024 07:47:33 +0100 Subject: [PATCH] Validate arguments of `CopyTo` --- .../FileSystemStream.cs | 74 ++++++++++++----- .../MockFileStreamTests.cs | 80 +++++++++++++++++++ 2 files changed, 136 insertions(+), 18 deletions(-) diff --git a/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs b/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs index 21e9a85d8..e0371c343 100644 --- a/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs +++ b/src/TestableIO.System.IO.Abstractions/FileSystemStream.cs @@ -103,18 +103,23 @@ public override IAsyncResult BeginWrite(byte[] buffer, /// #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); + } /// 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); + } /// public override int EndRead(IAsyncResult asyncResult) @@ -141,9 +146,9 @@ public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); #if FEATURE_SPAN - /// - public override int Read(Span buffer) - => _stream.Read(buffer); + /// + public override int Read(Span buffer) + => _stream.Read(buffer); #endif /// @@ -154,10 +159,10 @@ public override Task ReadAsync(byte[] buffer, => _stream.ReadAsync(buffer, offset, count, cancellationToken); #if FEATURE_SPAN - /// - public override ValueTask ReadAsync(Memory buffer, - CancellationToken cancellationToken = new()) - => _stream.ReadAsync(buffer, cancellationToken); + /// + public override ValueTask ReadAsync(Memory buffer, + CancellationToken cancellationToken = new()) + => _stream.ReadAsync(buffer, cancellationToken); #endif /// @@ -181,9 +186,9 @@ public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count); #if FEATURE_SPAN - /// - public override void Write(ReadOnlySpan buffer) - => _stream.Write(buffer); + /// + public override void Write(ReadOnlySpan buffer) + => _stream.Write(buffer); #endif /// @@ -194,10 +199,10 @@ public override Task WriteAsync(byte[] buffer, => _stream.WriteAsync(buffer, offset, count, cancellationToken); #if FEATURE_SPAN - /// - public override ValueTask WriteAsync(ReadOnlyMemory buffer, - CancellationToken cancellationToken = new()) - => _stream.WriteAsync(buffer, cancellationToken); + /// + public override ValueTask WriteAsync(ReadOnlyMemory buffer, + CancellationToken cancellationToken = new()) + => _stream.WriteAsync(buffer, cancellationToken); #endif /// @@ -220,5 +225,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."); + } + } } } \ No newline at end of file diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs index fcfb0e4a1..cbce9d4b7 100644 --- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs +++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs @@ -293,5 +293,85 @@ 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(async () => + await source.CopyToAsync(destination, bufferSize)); + } + + [Test] + public void MockFileStream_WhenDestinationIsClosed_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(); + destination.Close(); + + Assert.ThrowsAsync(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(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(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(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(async () => + await source.CopyToAsync(destination)); + } } }