Skip to content

Commit 17464e6

Browse files
authored
fix: validate arguments of CopyTo (#1161)
Validate arguments of `CopyTo`
1 parent 15320f7 commit 17464e6

File tree

2 files changed

+151
-18
lines changed

2 files changed

+151
-18
lines changed

src/TestableIO.System.IO.Abstractions/FileSystemStream.cs

+72-18
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,32 @@ public override IAsyncResult BeginWrite(byte[] buffer,
101101
object? state)
102102
=> _stream.BeginWrite(buffer, offset, count, callback, state);
103103

104+
/// <inheritdoc cref="Stream.Close()" />
105+
public override void Close()
106+
{
107+
base.Close();
108+
_stream.Close();
109+
}
110+
104111
/// <inheritdoc cref="Stream.CopyTo(Stream, int)" />
105112
#if NETSTANDARD2_0 || NET462
106-
public new virtual void CopyTo(Stream destination, int bufferSize)
107-
=> _stream.CopyTo(destination, bufferSize);
113+
public new virtual void CopyTo(Stream destination, int bufferSize)
108114
#else
109115
public override void CopyTo(Stream destination, int bufferSize)
110-
=> _stream.CopyTo(destination, bufferSize);
111116
#endif
117+
{
118+
ValidateCopyToArguments(this, destination, bufferSize);
119+
_stream.CopyTo(destination, bufferSize);
120+
}
112121

113122
/// <inheritdoc cref="Stream.CopyToAsync(Stream, int, CancellationToken)" />
114123
public override Task CopyToAsync(Stream destination,
115124
int bufferSize,
116125
CancellationToken cancellationToken)
117-
=> _stream.CopyToAsync(destination, bufferSize, cancellationToken);
126+
{
127+
ValidateCopyToArguments(this, destination, bufferSize);
128+
return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
129+
}
118130

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

143155
#if FEATURE_SPAN
144-
/// <inheritdoc cref="Stream.Read(Span{byte})" />
145-
public override int Read(Span<byte> buffer)
146-
=> _stream.Read(buffer);
156+
/// <inheritdoc cref="Stream.Read(Span{byte})" />
157+
public override int Read(Span<byte> buffer)
158+
=> _stream.Read(buffer);
147159
#endif
148160

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

156168
#if FEATURE_SPAN
157-
/// <inheritdoc cref="Stream.ReadAsync(Memory{byte}, CancellationToken)" />
158-
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
159-
CancellationToken cancellationToken = new())
160-
=> _stream.ReadAsync(buffer, cancellationToken);
169+
/// <inheritdoc cref="Stream.ReadAsync(Memory{byte}, CancellationToken)" />
170+
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
171+
CancellationToken cancellationToken = new())
172+
=> _stream.ReadAsync(buffer, cancellationToken);
161173
#endif
162174

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

183195
#if FEATURE_SPAN
184-
/// <inheritdoc cref="Stream.Write(ReadOnlySpan{byte})" />
185-
public override void Write(ReadOnlySpan<byte> buffer)
186-
=> _stream.Write(buffer);
196+
/// <inheritdoc cref="Stream.Write(ReadOnlySpan{byte})" />
197+
public override void Write(ReadOnlySpan<byte> buffer)
198+
=> _stream.Write(buffer);
187199
#endif
188200

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

196208
#if FEATURE_SPAN
197-
/// <inheritdoc cref="Stream.WriteAsync(ReadOnlyMemory{byte}, CancellationToken)" />
198-
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
199-
CancellationToken cancellationToken = new())
200-
=> _stream.WriteAsync(buffer, cancellationToken);
209+
/// <inheritdoc cref="Stream.WriteAsync(ReadOnlyMemory{byte}, CancellationToken)" />
210+
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
211+
CancellationToken cancellationToken = new())
212+
=> _stream.WriteAsync(buffer, cancellationToken);
201213
#endif
202214

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

226+
#if FEATURE_ASYNC_FILE
227+
/// <inheritdoc cref="Stream.DisposeAsync()" />
228+
public override async ValueTask DisposeAsync()
229+
{
230+
await _stream.DisposeAsync();
231+
await base.DisposeAsync();
232+
}
233+
#endif
234+
214235
/// <summary>
215236
/// Allows to cast the internal Stream to a FileStream
216237
/// </summary>
@@ -220,5 +241,38 @@ public static explicit operator FileStream(FileSystemStream fsStream)
220241
{
221242
return (FileStream) fsStream._stream;
222243
}
244+
245+
private static void ValidateCopyToArguments(Stream source, Stream destination, int bufferSize)
246+
{
247+
if (destination == null)
248+
{
249+
throw new ArgumentNullException(nameof(destination), "Destination cannot be null.");
250+
}
251+
252+
if (bufferSize <= 0)
253+
{
254+
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than zero.");
255+
}
256+
257+
if (!destination.CanWrite)
258+
{
259+
if (destination.CanRead)
260+
{
261+
throw new NotSupportedException("Stream does not support writing.");
262+
}
263+
264+
throw new ObjectDisposedException("Cannot access a closed Stream.");
265+
}
266+
267+
if (!source.CanRead)
268+
{
269+
if (source.CanWrite)
270+
{
271+
throw new NotSupportedException("Stream does not support reading.");
272+
}
273+
274+
throw new ObjectDisposedException("Cannot access a closed Stream.");
275+
}
276+
}
223277
}
224278
}

tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs

+79
Original file line numberDiff line numberDiff line change
@@ -293,5 +293,84 @@ public void MockFileStream_Null_ShouldHaveExpectedProperties()
293293
Assert.That(result.Length, Is.Zero);
294294
Assert.That(result.IsAsync, Is.True);
295295
}
296+
297+
[Test]
298+
[TestCase(0)]
299+
[TestCase(-1)]
300+
public void MockFileStream_WhenBufferSizeIsNotPositive_ShouldThrowArgumentNullException(int bufferSize)
301+
{
302+
var fileSystem = new MockFileSystem();
303+
fileSystem.File.WriteAllText("foo.txt", "");
304+
fileSystem.File.WriteAllText("bar.txt", "");
305+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
306+
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
307+
308+
Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
309+
await source.CopyToAsync(destination, bufferSize));
310+
}
311+
312+
[Test]
313+
public void MockFileStream_WhenDestinationIsClosed_ShouldThrowObjectDisposedException()
314+
{
315+
var fileSystem = new MockFileSystem();
316+
fileSystem.File.WriteAllText("foo.txt", "");
317+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
318+
using var destination = new MemoryStream();
319+
destination.Close();
320+
321+
Assert.ThrowsAsync<ObjectDisposedException>(async () =>
322+
await source.CopyToAsync(destination));
323+
}
324+
325+
[Test]
326+
public void MockFileStream_WhenDestinationIsNull_ShouldThrowArgumentNullException()
327+
{
328+
var fileSystem = new MockFileSystem();
329+
fileSystem.File.WriteAllText("foo.txt", "");
330+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
331+
332+
Assert.ThrowsAsync<ArgumentNullException>(async () =>
333+
await source.CopyToAsync(null));
334+
}
335+
336+
[Test]
337+
public void MockFileStream_WhenDestinationIsReadOnly_ShouldThrowNotSupportedException()
338+
{
339+
var fileSystem = new MockFileSystem();
340+
fileSystem.File.WriteAllText("foo.txt", "");
341+
fileSystem.File.WriteAllText("bar.txt", "");
342+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
343+
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenRead();
344+
345+
Assert.ThrowsAsync<NotSupportedException>(async () =>
346+
await source.CopyToAsync(destination));
347+
}
348+
349+
[Test]
350+
public void MockFileStream_WhenSourceIsClosed_ShouldThrowObjectDisposedException()
351+
{
352+
var fileSystem = new MockFileSystem();
353+
fileSystem.File.WriteAllText("foo.txt", "");
354+
fileSystem.File.WriteAllText("bar.txt", "");
355+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenRead();
356+
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
357+
source.Close();
358+
359+
Assert.ThrowsAsync<ObjectDisposedException>(async () =>
360+
await source.CopyToAsync(destination));
361+
}
362+
363+
[Test]
364+
public void MockFileStream_WhenSourceIsWriteOnly_ShouldThrowNotSupportedException()
365+
{
366+
var fileSystem = new MockFileSystem();
367+
fileSystem.File.WriteAllText("foo.txt", "");
368+
fileSystem.File.WriteAllText("bar.txt", "");
369+
using var source = fileSystem.FileInfo.New(@"foo.txt").OpenWrite();
370+
using var destination = fileSystem.FileInfo.New(@"bar.txt").OpenWrite();
371+
372+
Assert.ThrowsAsync<NotSupportedException>(async () =>
373+
await source.CopyToAsync(destination));
374+
}
296375
}
297376
}

0 commit comments

Comments
 (0)