add extension methods for checks

This commit is contained in:
Adam Hathcock
2026-04-23 10:11:54 +01:00
parent be4b6cdd7f
commit 2bae46e28a
21 changed files with 141 additions and 102 deletions

View File

@@ -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<T>();

View File

@@ -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<Stream> 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<T>(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<T>();
@@ -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;

View File

@@ -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)

View File

@@ -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(

View File

@@ -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)

View File

@@ -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],

View File

@@ -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<ZipWriterOptions>)OpenArchive(streams, readerOptions));
}

View File

@@ -19,7 +19,7 @@ public partial class AceReader
/// <returns>An AceReader instance.</returns>
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
/// <returns></returns>
public static IReader OpenReader(IEnumerable<Stream> 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<IAsyncReader> 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<IAsyncReader> OpenAsyncReader(

View File

@@ -12,7 +12,7 @@ internal class SingleVolumeAceReader : AceReader
internal SingleVolumeAceReader(Stream stream, ReaderOptions options)
: base(options)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
_stream = stream;
}

View File

@@ -24,7 +24,7 @@ public partial class ArcReader : AbstractReader<ArcEntry, ArcVolume>
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
return new ArcReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}

View File

@@ -31,7 +31,7 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
/// <returns></returns>
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<ArjEntry, ArjVolume>
/// <returns></returns>
public static IReader OpenReader(IEnumerable<Stream> 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);

View File

@@ -12,7 +12,7 @@ internal class SingleVolumeArjReader : ArjReader
internal SingleVolumeArjReader(Stream stream, ReaderOptions options)
: base(options)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
_stream = stream;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -71,7 +71,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
/// <returns></returns>
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<RarReaderEntry, RarVolu
/// <returns></returns>
public static IReader OpenReader(IEnumerable<Stream> 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<RarReaderEntry> GetEntries(Stream stream)

View File

@@ -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(

View File

@@ -29,7 +29,7 @@ public static partial class ReaderFactory
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? options = null)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
options ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = SharpCompressStream.Create(

View File

@@ -170,7 +170,7 @@ public partial class TarReader
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
readerOptions ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = SharpCompressStream.Create(
stream,

View File

@@ -47,7 +47,7 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
/// <returns></returns>
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<ZipEntry, ZipVolume>
IEnumerable<ZipEntry> entries
)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
return new ZipReader(stream, options ?? ReaderOptions.ForExternalStream, entries);
}

View File

@@ -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<Stream> RequireSeekable(this IReadOnlyList<Stream> streams)
{
streams.NotNull(nameof(streams));
foreach (var stream in streams)
{
stream.RequireSeekable();
}
return streams;
}
internal static IReadOnlyList<Stream> RequireReadable(this IEnumerable<Stream> streams)
{
streams.NotNull(nameof(streams));
var streamArray = streams as IReadOnlyList<Stream> ?? streams.ToArray();
foreach (var stream in streamArray)
{
stream.RequireReadable();
}
return streamArray;
}
}

View File

@@ -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<ArgumentException>(() => 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<ArgumentException>(() =>
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<ArgumentException>(() => RarReader.OpenReader([unreadable, readable]));
}
}