diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index 0d5415e1..39ac2795 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -99,7 +99,7 @@ public static partial class ArchiveFactory throw new ArchiveOperationException("No streams"); } - EnsureSeekable(streamsArray); + streamsArray.RequireSeekable(); var firstStream = streamsArray[0]; if (streamsArray.Count == 1) @@ -145,11 +145,8 @@ public static partial class ArchiveFactory ) where T : IFactory { - stream.NotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } + stream.RequireReadable(); + stream.RequireSeekable(); var factories = Factory.Factories.OfType(); diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 308aaa63..eb7a6815 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -13,22 +13,6 @@ namespace SharpCompress.Archives; public static partial class ArchiveFactory { - internal static void EnsureSeekable(Stream stream) - { - if (stream is null || !stream.CanSeek) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - } - - internal static void EnsureSeekable(IReadOnlyList streams) - { - foreach (var stream in streams) - { - EnsureSeekable(stream); - } - } - public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) { readerOptions ??= ReaderOptions.ForExternalStream; @@ -96,7 +80,7 @@ public static partial class ArchiveFactory throw new ArchiveOperationException("No streams"); } - EnsureSeekable(streamsArray); + streamsArray.RequireSeekable(); var firstStream = streamsArray[0]; if (streamsArray.Count == 1) @@ -139,11 +123,8 @@ public static partial class ArchiveFactory public static T FindFactory(Stream stream) where T : IFactory { - stream.NotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } + stream.RequireReadable(); + stream.RequireSeekable(); var factories = Factory.Factories.OfType(); @@ -178,12 +159,8 @@ public static partial class ArchiveFactory public static bool IsArchive(Stream stream, out ArchiveType? type) { type = null; - stream.NotNull(nameof(stream)); - - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } + stream.RequireReadable(); + stream.RequireSeekable(); var startPosition = stream.Position; @@ -217,12 +194,8 @@ public static partial class ArchiveFactory CancellationToken cancellationToken = default ) { - stream.NotNull(nameof(stream)); - - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } + stream.RequireReadable(); + stream.RequireSeekable(); var startPosition = stream.Position; diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs index 6a0b6bac..c7f57f88 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs @@ -77,7 +77,7 @@ public partial class GZipArchive ) { streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; return new GZipArchive( new SourceStream( @@ -93,12 +93,7 @@ public partial class GZipArchive ReaderOptions? readerOptions = null ) { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); return new GZipArchive( new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs index 79d89718..b97e7497 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs @@ -58,12 +58,7 @@ public partial class RarArchive public static IRarArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); return new RarArchive( new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream) @@ -92,7 +87,7 @@ public partial class RarArchive ) { streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; return new RarArchive( new SourceStream( diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs index 011b9a86..e15354b1 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs @@ -72,7 +72,7 @@ public partial class SevenZipArchive ) { streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; return new SevenZipArchive( new SourceStream( @@ -85,12 +85,7 @@ public partial class SevenZipArchive public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); return new SevenZipArchive( new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream) diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 37abd21c..5eacb117 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -67,7 +67,7 @@ public partial class TarArchive ) { streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; var sourceStream = new SourceStream( strms[0], @@ -87,12 +87,7 @@ public partial class TarArchive ReaderOptions? readerOptions = null ) { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); return OpenArchive([stream], readerOptions); } @@ -104,10 +99,7 @@ public partial class TarArchive ) { stream.NotNull(nameof(stream)); - if (!stream.CanSeek) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); var sourceStream = new SourceStream( stream, i => null, @@ -164,7 +156,7 @@ public partial class TarArchive { cancellationToken.ThrowIfCancellationRequested(); streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; var sourceStream = new SourceStream( strms[0], diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs index 18d28114..d83d00e1 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -68,7 +68,7 @@ public partial class ZipArchive ) { streams.NotNull(nameof(streams)); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); var strms = streams; return new ZipArchive( new SourceStream( @@ -84,12 +84,7 @@ public partial class ZipArchive ReaderOptions? readerOptions = null ) { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } + stream.RequireSeekable(); return new ZipArchive( new SourceStream(stream, i => null, readerOptions ?? ReaderOptions.ForExternalStream) @@ -133,7 +128,7 @@ public partial class ZipArchive ) { cancellationToken.ThrowIfCancellationRequested(); - SharpCompress.Archives.ArchiveFactory.EnsureSeekable(streams); + streams.RequireSeekable(); return new((IWritableAsyncArchive)OpenArchive(streams, readerOptions)); } diff --git a/src/SharpCompress/Readers/Ace/AceReader.Factory.cs b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs index 5977e8de..98c3124a 100644 --- a/src/SharpCompress/Readers/Ace/AceReader.Factory.cs +++ b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs @@ -19,7 +19,7 @@ public partial class AceReader /// An AceReader instance. public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new SingleVolumeAceReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } @@ -31,8 +31,8 @@ public partial class AceReader /// public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { - streams.NotNull(nameof(streams)); - return new MultiVolumeAceReader(streams, options ?? ReaderOptions.ForExternalStream); + var streamArray = streams.RequireReadable(); + return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream); } public static ValueTask OpenAsyncReader( @@ -61,8 +61,8 @@ public partial class AceReader ReaderOptions? options = null ) { - streams.NotNull(nameof(streams)); - return new MultiVolumeAceReader(streams, options ?? ReaderOptions.ForExternalStream); + var streamArray = streams.RequireReadable(); + return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream); } public static ValueTask OpenAsyncReader( diff --git a/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs b/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs index ce42c6e1..ba782d23 100644 --- a/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs +++ b/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs @@ -12,7 +12,7 @@ internal class SingleVolumeAceReader : AceReader internal SingleVolumeAceReader(Stream stream, ReaderOptions options) : base(options) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); _stream = stream; } diff --git a/src/SharpCompress/Readers/Arc/ArcReader.cs b/src/SharpCompress/Readers/Arc/ArcReader.cs index c1b53366..e2a8303d 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.cs @@ -24,7 +24,7 @@ public partial class ArcReader : AbstractReader /// public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new ArcReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } diff --git a/src/SharpCompress/Readers/Arj/ArjReader.cs b/src/SharpCompress/Readers/Arj/ArjReader.cs index a0c4899c..79389b87 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.cs @@ -31,7 +31,7 @@ public abstract partial class ArjReader : AbstractReader /// public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new SingleVolumeArjReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } @@ -43,8 +43,8 @@ public abstract partial class ArjReader : AbstractReader /// public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { - streams.NotNull(nameof(streams)); - return new MultiVolumeArjReader(streams, options ?? ReaderOptions.ForExternalStream); + var streamArray = streams.RequireReadable(); + return new MultiVolumeArjReader(streamArray, options ?? ReaderOptions.ForExternalStream); } protected abstract void ValidateArchive(ArjVolume archive); diff --git a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs index 71eb46e4..0128f60e 100644 --- a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs +++ b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs @@ -12,7 +12,7 @@ internal class SingleVolumeArjReader : ArjReader internal SingleVolumeArjReader(Stream stream, ReaderOptions options) : base(options) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); _stream = stream; } diff --git a/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs index bd593f20..a6bca9f6 100644 --- a/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs +++ b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs @@ -55,7 +55,7 @@ public partial class GZipReader public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new GZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } } diff --git a/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs b/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs index e2166e37..a1535c89 100644 --- a/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs +++ b/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs @@ -55,7 +55,7 @@ public partial class LzwReader public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new LzwReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 81a5457c..43925d64 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -71,7 +71,7 @@ public abstract partial class RarReader : AbstractReader public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new SingleVolumeRarReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } @@ -83,8 +83,8 @@ public abstract partial class RarReader : AbstractReader public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { - streams.NotNull(nameof(streams)); - return new MultiVolumeRarReader(streams, options ?? ReaderOptions.ForExternalStream); + var streamArray = streams.RequireReadable(); + return new MultiVolumeRarReader(streamArray, options ?? ReaderOptions.ForExternalStream); } protected override IEnumerable GetEntries(Stream stream) diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs index 80388577..72bd7b09 100644 --- a/src/SharpCompress/Readers/ReaderFactory.Async.cs +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -56,7 +56,7 @@ public static partial class ReaderFactory CancellationToken cancellationToken = default ) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); options ??= ReaderOptions.ForExternalStream; var sharpCompressStream = SharpCompressStream.Create( diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 2a84913c..72c4dd71 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -29,7 +29,7 @@ public static partial class ReaderFactory /// public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); options ??= ReaderOptions.ForExternalStream; var sharpCompressStream = SharpCompressStream.Create( diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs index aae20bb4..aa13c779 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -170,7 +170,7 @@ public partial class TarReader /// public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); readerOptions ??= ReaderOptions.ForExternalStream; var sharpCompressStream = SharpCompressStream.Create( stream, diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index e2019de6..a9ca5c50 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -47,7 +47,7 @@ public partial class ZipReader : AbstractReader /// public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new ZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream); } @@ -57,7 +57,7 @@ public partial class ZipReader : AbstractReader IEnumerable entries ) { - stream.NotNull(nameof(stream)); + stream.RequireReadable(); return new ZipReader(stream, options ?? ReaderOptions.ForExternalStream, entries); } diff --git a/src/SharpCompress/StreamValidationExtensions.cs b/src/SharpCompress/StreamValidationExtensions.cs new file mode 100644 index 00000000..7420bcec --- /dev/null +++ b/src/SharpCompress/StreamValidationExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace SharpCompress; + +internal static class StreamValidationExtensions +{ + internal static Stream RequireReadable(this Stream stream) + { + stream.NotNull(nameof(stream)); + + if (!stream.CanRead) + { + throw new ArgumentException("Stream must be readable", nameof(stream)); + } + + return stream; + } + + internal static Stream RequireSeekable(this Stream stream) + { + stream.NotNull(nameof(stream)); + + if (!stream.CanSeek) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return stream; + } + + internal static IReadOnlyList RequireSeekable(this IReadOnlyList streams) + { + streams.NotNull(nameof(streams)); + + foreach (var stream in streams) + { + stream.RequireSeekable(); + } + + return streams; + } + + internal static IReadOnlyList RequireReadable(this IEnumerable streams) + { + streams.NotNull(nameof(streams)); + + var streamArray = streams as IReadOnlyList ?? streams.ToArray(); + foreach (var stream in streamArray) + { + stream.RequireReadable(); + } + + return streamArray; + } +} diff --git a/tests/SharpCompress.Test/ReaderFactoryTests.cs b/tests/SharpCompress.Test/ReaderFactoryTests.cs new file mode 100644 index 00000000..f77fb2f9 --- /dev/null +++ b/tests/SharpCompress.Test/ReaderFactoryTests.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SharpCompress.Readers; +using SharpCompress.Readers.Rar; +using SharpCompress.Test.Mocks; +using Xunit; + +namespace SharpCompress.Test; + +public class ReaderFactoryTests +{ + [Fact] + public void OpenReader_Stream_Throws_On_Unreadable_Stream() + { + using var unreadable = new TestStream(new MemoryStream(), false, true, true); + + Assert.Throws(() => ReaderFactory.OpenReader(unreadable)); + } + + [Fact] + public async ValueTask OpenAsyncReader_Stream_Throws_On_Unreadable_Stream() + { + using var unreadable = new TestStream(new MemoryStream(), false, true, true); + + await Assert.ThrowsAsync(() => + ReaderFactory.OpenAsyncReader(unreadable).AsTask() + ); + } + + [Fact] + public void RarReader_StreamCollection_Throws_On_Unreadable_Stream() + { + using var unreadable = new TestStream(new MemoryStream(), false, true, true); + using var readable = new MemoryStream(); + + Assert.Throws(() => RarReader.OpenReader([unreadable, readable])); + } +}