Merge remote-tracking branch 'origin/master' into adam/write-async

# Conflicts:
#	src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs
#	src/SharpCompress/Factories/TarFactory.cs
#	src/SharpCompress/Polyfills/StreamExtensions.cs
#	src/SharpCompress/Writers/IWriter.cs
This commit is contained in:
Adam Hathcock
2026-05-14 16:12:13 +01:00
134 changed files with 4378 additions and 4543 deletions

View File

@@ -40,7 +40,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
internal AbstractArchive(ArchiveType type)
{
Type = type;
ReaderOptions = new();
ReaderOptions = ReaderOptions.Default;
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
@@ -34,7 +31,11 @@ public static partial class ArchiveFactory
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken);
return OpenAsyncArchive(
new FileInfo(filePath),
options ?? ReaderOptions.ForFilePath,
cancellationToken
);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
@@ -53,20 +54,20 @@ public static partial class ArchiveFactory
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
fileInfos.NotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
var filesArray = fileInfos;
if (filesArray.Count == 0)
{
throw new ArchiveOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
if (filesArray.Count == 1)
{
return await OpenAsyncArchive(fileInfo, options, cancellationToken)
.ConfigureAwait(false);
@@ -83,21 +84,15 @@ public static partial class ArchiveFactory
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
streams.NotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
throw new ArchiveOperationException("No streams");
}
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
if (streamsArray.Count == 1)
{
return await OpenAsyncArchive(firstStream, options, cancellationToken)
.ConfigureAwait(false);
@@ -112,64 +107,4 @@ public static partial class ArchiveFactory
.OpenAsyncArchive(streamsArray, options, cancellationToken)
.ConfigureAwait(false);
}
public static ValueTask<T> FindFactoryAsync<T>(
string path,
CancellationToken cancellationToken = default
)
where T : IFactory
{
path.NotNullOrEmpty(nameof(path));
return FindFactoryAsync<T>(new FileInfo(path), cancellationToken);
}
private static async ValueTask<T> FindFactoryAsync<T>(
FileInfo finfo,
CancellationToken cancellationToken
)
where T : IFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
}
private static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken
)
where T : IFactory
{
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (
await factory
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false)
)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
}

View File

@@ -0,0 +1,264 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
public static partial class ArchiveFactory
{
/// <summary>
/// Returns information about the archive at the given file path asynchronously,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
string filePath,
CancellationToken cancellationToken = default
) =>
await GetArchiveInformationAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
.ConfigureAwait(false);
/// <summary>
/// Returns information about the archive at the given file path asynchronously,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
string filePath,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return await GetArchiveInformationAsync(
stream,
readerOptions ?? ReaderOptions.ForFilePath,
cancellationToken
)
.ConfigureAwait(false);
}
/// <summary>
/// Returns information about the archive in the given stream asynchronously,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await GetArchiveInformationAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
/// <summary>
/// Returns information about the archive in the given stream asynchronously,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
stream.RequireReadable();
stream.RequireSeekable();
var factory = await TryFindFactoryAsync(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
cancellationToken
)
.ConfigureAwait(false);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
}
internal static ValueTask<T> FindFactoryAsync<T>(
string filePath,
CancellationToken cancellationToken = default
)
where T : IFactory
{
filePath.NotNullOrEmpty(nameof(filePath));
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
FileInfo fileInfo,
CancellationToken cancellationToken = default
)
where T : IFactory
{
fileInfo.NotNull(nameof(fileInfo));
using Stream stream = fileInfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken = default
)
where T : IFactory
{
stream.RequireReadable();
stream.RequireSeekable();
// Use the shared async detection loop over all factories. If the matched factory
// implements T we return it; otherwise (or if nothing matched) we fall through
// to the same "unsupported format" exception that the original code produced,
// listing the T-typed factories as the hint for the caller.
var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false);
if (factory is T typedFactory)
{
return typedFactory;
}
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
/// <summary>
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchiveAsync"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
private static async ValueTask<IFactory?> TryFindFactoryAsync(
Stream stream,
CancellationToken cancellationToken
) =>
await TryFindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
private static async ValueTask<IFactory?> TryFindFactoryAsync(
Stream stream,
ReaderOptions readerOptions,
CancellationToken cancellationToken
)
{
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
var isArchive = await factory
.IsArchiveAsync(stream, readerOptions, cancellationToken)
.ConfigureAwait(false);
if (isArchive)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
return null;
}
/// <summary>
/// Returns information about the archive at the given file path,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
public static ArchiveInformation? GetArchiveInformation(string filePath) =>
GetArchiveInformation(filePath, ReaderOptions.ForFilePath);
/// <summary>
/// Returns information about the archive at the given file path,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
public static ArchiveInformation? GetArchiveInformation(
string filePath,
ReaderOptions? readerOptions
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return GetArchiveInformation(stream, readerOptions ?? ReaderOptions.ForFilePath);
}
/// <summary>
/// Returns information about the archive in the given stream,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
public static ArchiveInformation? GetArchiveInformation(Stream stream) =>
GetArchiveInformation(stream, ReaderOptions.ForExternalStream);
/// <summary>
/// Returns information about the archive in the given stream,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
public static ArchiveInformation? GetArchiveInformation(
Stream stream,
ReaderOptions? readerOptions
)
{
stream.RequireReadable();
stream.RequireSeekable();
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
}
/// <summary>
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchive"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
/// <remarks>
/// This is the shared, seekable-stream detection core used by
/// <see cref="FindFactory{T}(Stream)"/>, <see cref="IsArchive(Stream, out ArchiveType?)"/>,
/// and <see cref="GetArchiveInformation(Stream)"/>.
/// <para>
/// <see cref="ReaderFactory.OpenReader(Stream, ReaderOptions)"/> uses a separate code path
/// based on <see cref="IO.SharpCompressStream"/> rewindable buffering, which supports
/// non-seekable streams and is therefore not unified with this helper.
/// </para>
/// </remarks>
private static IFactory? TryFindFactory(Stream stream) =>
TryFindFactory(stream, ReaderOptions.ForExternalStream);
private static IFactory? TryFindFactory(Stream stream, ReaderOptions readerOptions)
{
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
var isArchive = factory.IsArchive(stream, readerOptions);
if (isArchive)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
return null;
}
}

View File

@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Factories;
@@ -21,7 +23,7 @@ public static partial class ArchiveFactory
where TOptions : IWriterOptions
{
var factory = Factory
.Factories.OfType<IWriteableArchiveFactory<TOptions>>()
.Factories.OfType<IWritableArchiveFactory<TOptions>>()
.FirstOrDefault();
if (factory != null)
@@ -35,7 +37,7 @@ public static partial class ArchiveFactory
public static IArchive OpenArchive(string filePath, ReaderOptions? options = null)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), options);
return OpenArchive(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
}
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null)
@@ -46,19 +48,19 @@ public static partial class ArchiveFactory
}
public static IArchive OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? options = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
var filesArray = fileInfos;
if (filesArray.Count == 0)
{
throw new ArchiveOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
if (filesArray.Count == 1)
{
return OpenArchive(fileInfo, options);
}
@@ -69,17 +71,16 @@ public static partial class ArchiveFactory
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
}
public static IArchive OpenArchive(IEnumerable<Stream> streams, ReaderOptions? options = null)
public static IArchive OpenArchive(IReadOnlyList<Stream> streams, ReaderOptions? options = null)
{
streams.NotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
if (streamsArray.Count == 0)
{
throw new ArchiveOperationException("No streams");
}
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
if (streamsArray.Count == 1)
{
return OpenArchive(firstStream, options);
}
@@ -100,11 +101,11 @@ public static partial class ArchiveFactory
archive.WriteToDirectory(destinationDirectory, options);
}
public static T FindFactory<T>(string path)
public static T FindFactory<T>(string filePath)
where T : IFactory
{
path.NotNullOrEmpty(nameof(path));
using Stream stream = File.OpenRead(path);
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return FindFactory<T>(stream);
}
@@ -119,29 +120,20 @@ public static partial class ArchiveFactory
public static T FindFactory<T>(Stream stream)
where T : IFactory
{
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
stream.RequireReadable();
stream.RequireSeekable();
// Use the shared detection loop over all factories. If the matched factory
// implements T we return it; otherwise (or if nothing matched) we fall through
// to the same "unsupported format" exception that the original code produced,
// listing the T-typed factories as the hint for the caller.
var factory = TryFindFactory(stream);
if (factory is T typedFactory)
{
throw new ArgumentException("Stream should be readable and seekable");
return typedFactory;
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (factory.IsArchive(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
@@ -149,37 +141,82 @@ public static partial class ArchiveFactory
}
public static bool IsArchive(string filePath, out ArchiveType? type)
{
return IsArchive(filePath, ReaderOptions.ForFilePath, out type);
}
public static bool IsArchive(
string filePath,
ReaderOptions? readerOptions,
out ArchiveType? type
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream s = File.OpenRead(filePath);
return IsArchive(s, out type);
return IsArchive(s, readerOptions ?? ReaderOptions.ForFilePath, out type);
}
public static bool IsArchive(Stream stream, out ArchiveType? type)
{
type = null;
stream.NotNull(nameof(stream));
return IsArchive(stream, ReaderOptions.ForExternalStream, out type);
}
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
public static bool IsArchive(Stream stream, ReaderOptions? readerOptions, out ArchiveType? type)
{
stream.RequireReadable();
stream.RequireSeekable();
var startPosition = stream.Position;
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
type = factory?.KnownArchiveType;
return factory is not null;
}
foreach (var factory in Factory.Factories)
{
var isArchive = factory.IsArchive(stream);
stream.Position = startPosition;
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
string filePath,
CancellationToken cancellationToken = default
) =>
await IsArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
.ConfigureAwait(false);
if (isArchive)
{
type = factory.KnownArchiveType;
return true;
}
}
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
string filePath,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return await IsArchiveAsync(
stream,
readerOptions ?? ReaderOptions.ForFilePath,
cancellationToken
)
.ConfigureAwait(false);
}
return false;
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await IsArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
stream.RequireReadable();
stream.RequireSeekable();
var factory = await TryFindFactoryAsync(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
cancellationToken
)
.ConfigureAwait(false);
return (factory is not null, factory?.KnownArchiveType);
}
public static IEnumerable<string> GetFileParts(string part1)

View File

@@ -0,0 +1,22 @@
using SharpCompress.Common;
namespace SharpCompress.Archives;
/// <summary>
/// Contains information about a detected archive, including its type and supported capabilities.
/// </summary>
/// <remarks>
/// Use <see cref="ArchiveFactory.GetArchiveInformation(System.IO.Stream)"/> or
/// <see cref="ArchiveFactory.GetArchiveInformationAsync(System.IO.Stream,System.Threading.CancellationToken)"/>
/// to obtain an instance of this record.
/// </remarks>
/// <param name="Type">
/// The type of archive detected, or <see langword="null"/> when the format is not a registered well-known type.
/// </param>
/// <param name="SupportsRandomAccess">
/// <see langword="true"/> when this archive format supports random access via the <see cref="IArchive"/> API,
/// meaning the full file listing can be retrieved without decompressing the entire archive.
/// <see langword="false"/> when only the <see cref="SharpCompress.Readers.IReader"/> API is available,
/// which reads entries sequentially and can only report per-entry progress.
/// </param>
public record ArchiveInformation(ArchiveType? Type, bool SupportsRandomAccess);

View File

@@ -21,14 +21,14 @@ public partial class GZipArchive
#endif
{
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
}
public static IWritableArchive<GZipWriterOptions> OpenArchive(
@@ -37,7 +37,7 @@ public partial class GZipArchive
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
}
public static IWritableArchive<GZipWriterOptions> OpenArchive(
@@ -50,39 +50,38 @@ public partial class GZipArchive
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
return new GZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
return new GZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
)
);
}
@@ -92,15 +91,11 @@ 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.RequireReadable();
stream.RequireSeekable();
return new GZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
);
}

View File

@@ -20,7 +20,7 @@ public interface IArchiveOpenable<TSync, TASync>
public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null);
public static abstract ValueTask<TASync> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);

View File

@@ -12,12 +12,12 @@ public interface IMultiArchiveOpenable<TSync, TASync>
where TASync : IAsyncArchive
{
public static abstract TSync OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
);
public static abstract TSync OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
);

View File

@@ -1,8 +1,6 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
@@ -11,7 +9,7 @@ public static class IWritableArchiveExtensions
extension(IWritableArchive writableArchive)
{
public void AddAllFromDirectory(
string filePath,
string directoryPath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
@@ -19,12 +17,16 @@ public static class IWritableArchiveExtensions
using (writableArchive.PauseEntryRebuilding())
{
foreach (
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
var filePath in Directory.EnumerateFiles(
directoryPath,
searchPattern,
searchOption
)
)
{
var fileInfo = new FileInfo(path);
var fileInfo = new FileInfo(filePath);
writableArchive.AddEntry(
path.Substring(filePath.Length),
filePath.Substring(directoryPath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,

View File

@@ -3,7 +3,7 @@ using SharpCompress.Common.Options;
namespace SharpCompress.Archives;
/// <summary>
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writeable archives
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writable archives.
/// </summary>
/// <remarks>
/// Implemented by:<br/>
@@ -12,7 +12,8 @@ namespace SharpCompress.Archives;
/// <item><see cref="Factories.ZipFactory"/></item>
/// <item><see cref="Factories.GZipFactory"/></item>
/// </list>
public interface IWriteableArchiveFactory<TOptions> : Factories.IFactory
/// </remarks>
public interface IWritableArchiveFactory<TOptions> : Factories.IFactory
where TOptions : IWriterOptions
{
/// <summary>

View File

@@ -2,9 +2,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
@@ -13,7 +11,7 @@ public static class IWritableAsyncArchiveExtensions
extension(IWritableAsyncArchive writableArchive)
{
public async ValueTask AddAllFromDirectoryAsync(
string filePath,
string directoryPath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
@@ -21,13 +19,17 @@ public static class IWritableAsyncArchiveExtensions
using (writableArchive.PauseEntryRebuilding())
{
foreach (
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
var filePath in Directory.EnumerateFiles(
directoryPath,
searchPattern,
searchOption
)
)
{
var fileInfo = new FileInfo(path);
var fileInfo = new FileInfo(filePath);
await writableArchive
.AddEntryAsync(
path.Substring(filePath.Length),
filePath.Substring(directoryPath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,

View File

@@ -15,18 +15,17 @@ namespace SharpCompress.Archives.Rar;
internal class FileInfoRarArchiveVolume : RarVolume
{
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
: base(
StreamingMode.Seekable,
fileInfo.OpenRead(),
options.WithLeaveStreamOpen(false),
index
)
{
FileInfo = fileInfo;
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
}
private static ReaderOptions FixOptions(ReaderOptions options)
{
//make sure we're closing streams with fileinfo
return options with { LeaveStreamOpen = false };
}
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
internal FileInfo FileInfo { get; }

View File

@@ -21,14 +21,14 @@ public partial class RarArchive
#endif
{
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IRarAsyncArchive)OpenArchive(new FileInfo(filePath), readerOptions));
}
public static IRarArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
@@ -39,7 +39,7 @@ public partial class RarArchive
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
@@ -51,53 +51,48 @@ public partial class RarArchive
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
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.RequireReadable();
stream.RequireSeekable();
return new RarArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
);
}
public static IRarArchive OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
return new RarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IRarArchive OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
return new RarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
)
);
}
@@ -158,7 +153,7 @@ public partial class RarArchive
{
try
{
MarkHeader.Read(stream, true, false);
MarkHeader.Read(stream, true, options?.LookForHeader ?? false);
return true;
}
catch
@@ -177,7 +172,7 @@ public partial class RarArchive
try
{
await MarkHeader
.ReadAsync(stream, true, false, cancellationToken)
.ReadAsync(stream, true, options?.LookForHeader ?? false, cancellationToken)
.ConfigureAwait(false);
return true;
}

View File

@@ -17,22 +17,25 @@ public partial class SevenZipArchive
#endif
{
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty("path");
filePath.NotNullOrEmpty(nameof(filePath));
return new(
(IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions())
(IAsyncArchive)OpenArchive(
new FileInfo(filePath),
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
{
filePath.NotNullOrEmpty("filePath");
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
}
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null)
@@ -42,54 +45,49 @@ public partial class SevenZipArchive
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IArchive OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
return new SevenZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IArchive OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
return new SevenZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
)
);
}
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.RequireReadable();
stream.RequireSeekable();
return new SevenZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
);
}
@@ -145,11 +143,14 @@ public partial class SevenZipArchive
return IsSevenZipFile(stream);
}
public static bool IsSevenZipFile(Stream stream)
public static bool IsSevenZipFile(Stream stream) =>
IsSevenZipFile(stream, ReaderOptions.ForExternalStream);
public static bool IsSevenZipFile(Stream stream, ReaderOptions? readerOptions)
{
try
{
return SignatureMatch(stream);
return SignatureMatch(stream, readerOptions?.LookForHeader ?? false);
}
catch
{
@@ -160,12 +161,25 @@ public partial class SevenZipArchive
public static async ValueTask<bool> IsSevenZipFileAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await IsSevenZipFileAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
public static async ValueTask<bool> IsSevenZipFileAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
return await SignatureMatchAsync(stream, cancellationToken).ConfigureAwait(false);
return await SignatureMatchAsync(
stream,
readerOptions?.LookForHeader ?? false,
cancellationToken
)
.ConfigureAwait(false);
}
catch
{
@@ -175,13 +189,29 @@ public partial class SevenZipArchive
private static ReadOnlySpan<byte> Signature => [(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
private static bool SignatureMatch(Stream stream)
private static bool SignatureMatch(Stream stream, bool lookForHeader)
{
var buffer = ArrayPool<byte>.Shared.Rent(6);
try
{
stream.ReadExact(buffer, 0, 6);
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
for (var offset = 0; offset <= maxScanOffset; offset++)
{
stream.ReadExact(buffer, 0, 6);
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
{
return true;
}
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
{
return false;
}
stream.Position -= 5;
}
return false;
}
finally
{
@@ -191,18 +221,39 @@ public partial class SevenZipArchive
private static async ValueTask<bool> SignatureMatchAsync(
Stream stream,
bool lookForHeader,
CancellationToken cancellationToken
)
{
var buffer = ArrayPool<byte>.Shared.Rent(6);
try
{
if (!await stream.ReadFullyAsync(buffer, 0, 6, cancellationToken).ConfigureAwait(false))
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
for (var offset = 0; offset <= maxScanOffset; offset++)
{
return false;
if (
!await stream
.ReadFullyAsync(buffer, 0, 6, cancellationToken)
.ConfigureAwait(false)
)
{
return false;
}
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
{
return true;
}
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
{
return false;
}
stream.Position -= 5;
}
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
return false;
}
finally
{

View File

@@ -141,7 +141,7 @@ public partial class TarArchive
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
using var memoryStream = new PooledMemoryStream();
await entryStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();

View File

@@ -38,23 +38,20 @@ public partial class TarArchive
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenArchive(
[fileInfo],
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
);
return OpenArchive([fileInfo], readerOptions ?? ReaderOptions.ForFilePath);
}
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
var sourceStream = new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
);
var compressionType = TarFactory.GetCompressionType(
sourceStream,
@@ -65,16 +62,15 @@ public partial class TarArchive
}
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
var sourceStream = new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
);
var compressionType = TarFactory.GetCompressionType(
sourceStream,
@@ -89,12 +85,8 @@ 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.RequireReadable();
stream.RequireSeekable();
return OpenArchive([stream], readerOptions);
}
@@ -105,11 +97,12 @@ public partial class TarArchive
CancellationToken cancellationToken = default
)
{
stream.NotNull(nameof(stream));
stream.RequireReadable();
stream.RequireSeekable();
var sourceStream = new SourceStream(
stream,
i => null,
readerOptions ?? new ReaderOptions()
readerOptions ?? ReaderOptions.ForExternalStream
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
@@ -119,14 +112,14 @@ public partial class TarArchive
}
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
}
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
@@ -137,7 +130,7 @@ public partial class TarArchive
{
cancellationToken.ThrowIfCancellationRequested();
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
readerOptions ??= ReaderOptions.ForFilePath;
var sourceStream = new SourceStream(fileInfo, i => null, readerOptions);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
@@ -153,12 +146,11 @@ public partial class TarArchive
)
{
cancellationToken.ThrowIfCancellationRequested();
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
var sourceStream = new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
@@ -175,11 +167,11 @@ public partial class TarArchive
{
cancellationToken.ThrowIfCancellationRequested();
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
var sourceStream = new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)

View File

@@ -151,7 +151,7 @@ public partial class TarArchive
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
using var memoryStream = new PooledMemoryStream();
entryStream.CopyTo(memoryStream, Constants.BufferSize);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();

View File

@@ -45,7 +45,7 @@ public partial class ZipArchive
s = new SourceStream(
v[0].Stream,
i => i < v.Length ? v[i].Stream : null,
new ReaderOptions() { LeaveStreamOpen = true }
ReaderOptions.ForExternalStream
);
}
else

View File

@@ -41,39 +41,38 @@ public partial class ZipArchive
new SourceStream(
fileInfo,
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IWritableArchive<ZipWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var files = fileInfos;
return new ZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
)
);
}
public static IWritableArchive<ZipWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var strms = streams.RequireReadable().RequireSeekable().ToList();
return new ZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
)
);
}
@@ -83,26 +82,22 @@ 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.RequireReadable();
stream.RequireSeekable();
return new ZipArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
new SourceStream(stream, i => null, readerOptions ?? ReaderOptions.ForExternalStream)
);
}
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(path, readerOptions));
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(filePath, readerOptions));
}
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(

View File

@@ -86,7 +86,7 @@ public partial class ZipArchive
s = new SourceStream(
v[0].Stream,
i => i < v.Length ? v[i].Stream : null,
new ReaderOptions() { LeaveStreamOpen = true }
ReaderOptions.ForExternalStream
);
}
else

View File

@@ -45,7 +45,7 @@ public sealed record ExtractionOptions : IExtractionOptions
/// </summary>
/// <remarks>
/// <b>Breaking change:</b> Changed from field to init-only property in version 0.40.0.
/// The default handler logs a warning message.
/// If no handler is provided, symbolic links are silently skipped during extraction.
/// </remarks>
public Action<string, string>? SymbolicLinkHandler { get; init; }
@@ -58,10 +58,7 @@ public sealed record ExtractionOptions : IExtractionOptions
/// Creates a new ExtractionOptions instance with the specified overwrite behavior.
/// </summary>
/// <param name="overwrite">Whether to overwrite existing files.</param>
public ExtractionOptions(bool overwrite)
{
Overwrite = overwrite;
}
public ExtractionOptions(bool overwrite) => Overwrite = overwrite;
/// <summary>
/// Creates a new ExtractionOptions instance with the specified extraction path and overwrite behavior.
@@ -102,14 +99,4 @@ public sealed record ExtractionOptions : IExtractionOptions
/// </summary>
public static ExtractionOptions PreserveMetadata =>
new() { PreserveFileTime = true, PreserveAttributes = true };
/// <summary>
/// Default symbolic link handler that logs a warning message.
/// </summary>
public static void DefaultSymbolicLinkHandler(string sourcePath, string targetPath)
{
Console.WriteLine(
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
);
}
}

View File

@@ -5,11 +5,11 @@ namespace SharpCompress.Common.GZip;
public class GZipVolume : Volume
{
public GZipVolume(Stream stream, ReaderOptions? options, int index)
public GZipVolume(Stream stream, ReaderOptions options, int index)
: base(stream, options, index) { }
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
public override bool IsFirstVolume => true;

View File

@@ -1,50 +0,0 @@
using System.IO;
namespace SharpCompress.Common;
internal static class EntryExtensions
{
internal static void PreserveExtractionOptions(
this IEntry entry,
string destinationFileName,
ExtractionOptions options
)
{
if (options.PreserveFileTime || options.PreserveAttributes)
{
var nf = new FileInfo(destinationFileName);
if (!nf.Exists)
{
return;
}
// update file time to original packed time
if (options.PreserveFileTime)
{
if (entry.CreatedTime.HasValue)
{
nf.CreationTime = entry.CreatedTime.Value;
}
if (entry.LastModifiedTime.HasValue)
{
nf.LastWriteTime = entry.LastModifiedTime.Value;
}
if (entry.LastAccessedTime.HasValue)
{
nf.LastAccessTime = entry.LastAccessedTime.Value;
}
}
if (options.PreserveAttributes)
{
if (entry.Attrib.HasValue)
{
nf.Attributes = (FileAttributes)
System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
}
}
}
}
}

View File

@@ -86,18 +86,7 @@ internal static partial class IEntryExtensions
options ??= new ExtractionOptions();
if (entry.LinkTarget != null)
{
if (options.SymbolicLinkHandler is not null)
{
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
else
{
ExtractionOptions.DefaultSymbolicLinkHandler(
destinationFileName,
entry.LinkTarget
);
}
return;
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
}
else
{

View File

@@ -107,19 +107,7 @@ internal static partial class IEntryExtensions
options ??= new ExtractionOptions();
if (entry.LinkTarget != null)
{
if (options.SymbolicLinkHandler is not null)
{
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
else
{
ExtractionOptions.DefaultSymbolicLinkHandler(
destinationFileName,
entry.LinkTarget
);
}
return;
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
}
else
{
@@ -134,5 +122,69 @@ internal static partial class IEntryExtensions
entry.PreserveExtractionOptions(destinationFileName, options);
}
}
internal void PreserveExtractionOptions(
string destinationFileName,
ExtractionOptions options
)
{
if (options.PreserveFileTime || options.PreserveAttributes)
{
var nf = new FileInfo(destinationFileName);
if (!nf.Exists)
{
return;
}
// update file time to original packed time
if (options.PreserveFileTime)
{
if (entry.CreatedTime.HasValue)
{
try
{
nf.CreationTime = entry.CreatedTime.Value;
}
catch
{
// Invalid time or the OS rejected
}
}
if (entry.LastModifiedTime.HasValue)
{
try
{
nf.LastWriteTime = entry.LastModifiedTime.Value;
}
catch
{
// Invalid time or the OS rejected
}
}
if (entry.LastAccessedTime.HasValue)
{
try
{
nf.LastAccessTime = entry.LastAccessedTime.Value;
}
catch
{
// Invalid time or the OS rejected
}
}
}
if (options.PreserveAttributes)
{
if (entry.Attrib.HasValue)
{
nf.Attributes = (FileAttributes)
Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
}
}
}
}
}
}

View File

@@ -5,11 +5,11 @@ namespace SharpCompress.Common.Lzw;
public class LzwVolume : Volume
{
public LzwVolume(Stream stream, ReaderOptions? options, int index)
public LzwVolume(Stream stream, ReaderOptions options, int index)
: base(stream, options, index) { }
public LzwVolume(FileInfo fileInfo, ReaderOptions options)
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
public override bool IsFirstVolume => true;

View File

@@ -35,7 +35,7 @@ internal class CryptKey5 : ICryptKey
)
{
var passwordBytes = Encoding.UTF8.GetBytes(password);
#if LEGACY_DOTNET || NET5_0
#if LEGACY_DOTNET
using var hmac = new HMACSHA256(passwordBytes);
var block = hmac.ComputeHash(salt);
#else
@@ -50,7 +50,7 @@ internal class CryptKey5 : ICryptKey
{
for (var i = 1; i < loop[x]; i++)
{
#if LEGACY_DOTNET || NET5_0
#if LEGACY_DOTNET
block = hmac.ComputeHash(block);
#else
block = HMACSHA256.HashData(passwordBytes, block);

View File

@@ -1182,7 +1182,7 @@ internal partial class ArchiveReader
}
else
{
_stream = new MemoryStream();
_stream = new PooledMemoryStream();
}
_rem = _db._files[index].Size;
}

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Text;
using SharpCompress.Compressors.LZMA.Utilities;
using SharpCompress.IO;
namespace SharpCompress.Common.SevenZip;
@@ -215,7 +216,7 @@ internal sealed class SevenZipFilesInfoWriter
Action<Stream> writeData
)
{
using var dataStream = new MemoryStream();
using var dataStream = new PooledMemoryStream();
writeData(dataStream);
stream.WriteByte((byte)propertyId);

View File

@@ -59,7 +59,7 @@ internal sealed partial class TarHeader
int splitIndex = -1;
for (int i = 0; i < dirSeps.Count; i++)
{
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
int count = ArchiveEncoding
.GetEncoding()
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));

View File

@@ -89,7 +89,7 @@ internal sealed partial class TarHeader
int splitIndex = -1;
for (int i = 0; i < dirSeps.Count; i++)
{
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
int count = ArchiveEncoding
.GetEncoding()
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));

View File

@@ -11,10 +11,10 @@ public abstract partial class Volume : IVolume, IAsyncDisposable
private readonly Stream _baseStream;
private readonly Stream _actualStream;
internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0)
internal Volume(Stream stream, ReaderOptions readerOptions, int index = 0)
{
Index = index;
ReaderOptions = readerOptions ?? new ReaderOptions();
ReaderOptions = readerOptions;
_baseStream = stream;
// Only rewind if it's a buffered SharpCompressStream (not passthrough)

View File

@@ -270,7 +270,7 @@ internal sealed class Lzma2EncoderStream : Stream
}
using var inputMs = new MemoryStream(data.ToArray(), writable: false);
using var outputMs = new MemoryStream();
using var outputMs = new PooledMemoryStream();
encoder.Code(inputMs, outputMs, data.Length, -1, null);
@@ -302,7 +302,7 @@ internal sealed class Lzma2EncoderStream : Stream
decoder.SetDecoderProperties(props);
using var input = new MemoryStream(compressedData);
using var output = new MemoryStream();
using var output = new PooledMemoryStream();
decoder.Code(input, output, compressedData.Length, uncompressedSize, null);
return (int)input.Position;

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA.RangeCoder;
using SharpCompress.Compressors.PPMd.H;
using SharpCompress.Compressors.PPMd.I1;
using SharpCompress.IO;
namespace SharpCompress.Compressors.PPMd;
@@ -179,7 +180,7 @@ public class PpmdStream : Stream, IAsyncDisposable
{
if (_compress)
{
_model.EncodeBlock(_stream, new MemoryStream(), true);
_model.EncodeBlock(_stream, Stream.Null, true);
}
}
base.Dispose(disposing);

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
@@ -32,12 +31,12 @@ internal partial class RarBLAKE2spStream : RarStream
.ConfigureAwait(false);
if (result != 0)
{
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
Update(_blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
}
else
{
_hash = Final(_blake2sp);
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
EnsureHash();
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
{
// NOTE: we use the last FileHeader in a multipart volume to check CRC
throw new InvalidFormatException("file crc mismatch");
@@ -56,14 +55,14 @@ internal partial class RarBLAKE2spStream : RarStream
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (result != 0)
{
Update(_blake2sp, buffer.Span.Slice(0, result), result);
Update(_blake2sp!, buffer.Span.Slice(0, result));
}
else
{
_hash = Final(_blake2sp);
EnsureHash();
if (
!disableCRCCheck
&& !(GetCrc().SequenceEqual(readStream.CurrentCrc))
&& !GetCrc().SequenceEqual(readStream.CurrentCrc)
&& buffer.Length != 0
)
{

View File

@@ -1,8 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
@@ -13,14 +10,14 @@ internal partial class RarBLAKE2spStream : RarStream
private readonly MultiVolumeReadOnlyStreamBase readStream;
private readonly bool disableCRCCheck;
const uint BLAKE2S_NUM_ROUNDS = 10;
const uint BLAKE2S_FINAL_FLAG = (~(uint)0);
const int BLAKE2S_BLOCK_SIZE = 64;
const int BLAKE2S_DIGEST_SIZE = 32;
const int BLAKE2SP_PARALLEL_DEGREE = 8;
const uint BLAKE2S_INIT_IV_SIZE = 8;
private const int BLAKE2S_NUM_ROUNDS = 10;
private const uint BLAKE2S_FINAL_FLAG = ~(uint)0;
private const int BLAKE2S_BLOCK_SIZE = 64;
private const int BLAKE2S_DIGEST_SIZE = 32;
private const int BLAKE2SP_PARALLEL_DEGREE = 8;
private const int BLAKE2S_INIT_IV_SIZE = 8;
static readonly UInt32[] k_BLAKE2S_IV =
private static readonly uint[] k_BLAKE2S_IV =
{
0x6A09E667U,
0xBB67AE85U,
@@ -32,7 +29,7 @@ internal partial class RarBLAKE2spStream : RarStream
0x5BE0CD19U,
};
static readonly byte[][] k_BLAKE2S_Sigma =
private static readonly byte[][] k_BLAKE2S_Sigma =
{
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
new byte[] { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
@@ -46,14 +43,14 @@ internal partial class RarBLAKE2spStream : RarStream
new byte[] { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
};
internal class BLAKE2S
private sealed class BLAKE2S
{
internal UInt32[] h;
internal UInt32[] t;
internal UInt32[] f;
internal byte[] b;
internal readonly uint[] h;
internal readonly uint[] t;
internal readonly uint[] f;
internal readonly byte[] b;
internal int bufferPosition;
internal UInt32 lastNodeFlag;
internal uint lastNodeFlag;
public BLAKE2S()
{
@@ -64,9 +61,9 @@ internal partial class RarBLAKE2spStream : RarStream
}
};
internal class BLAKE2SP
private sealed class BLAKE2SP
{
internal BLAKE2S[] S;
internal readonly BLAKE2S[] S;
internal int bufferPosition;
public BLAKE2SP()
@@ -79,9 +76,8 @@ internal partial class RarBLAKE2spStream : RarStream
}
};
BLAKE2SP _blake2sp;
byte[] _hash = [];
private BLAKE2SP? _blake2sp;
private byte[]? _hash;
private RarBLAKE2spStream(
IRarUnpack unpack,
@@ -92,10 +88,9 @@ internal partial class RarBLAKE2spStream : RarStream
{
this.readStream = readStream;
// TODO: rar uses a modified hash xor'ed with encryption key?
disableCRCCheck = fileHeader.IsEncrypted;
_hash = fileHeader.FileCrc.NotNull();
_blake2sp = new BLAKE2SP();
ResetCrc();
this._blake2sp = CreateBlake2sp();
}
public static RarBLAKE2spStream Create(
@@ -111,19 +106,15 @@ internal partial class RarBLAKE2spStream : RarStream
// Async methods moved to RarBLAKE2spStream.Async.cs
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public byte[] GetCrc() =>
this._hash
?? throw new InvalidOperationException(
"hash not computed, has the stream been fully drained?"
);
public byte[] GetCrc() => _hash;
internal void ResetCrc(BLAKE2S hash)
private static void ResetCrc(BLAKE2S hash)
{
for (UInt32 j = 0; j < BLAKE2S_INIT_IV_SIZE; j++)
{
hash.h[j] = k_BLAKE2S_IV[j];
}
k_BLAKE2S_IV.AsSpan().CopyTo(hash.h);
hash.t[0] = 0;
hash.t[1] = 0;
hash.f[0] = 0;
@@ -132,14 +123,14 @@ internal partial class RarBLAKE2spStream : RarStream
hash.lastNodeFlag = 0;
}
internal void G(
ref UInt32[] m,
ref byte[] sigma,
private static void G(
Span<uint> m,
byte[] sigma,
int i,
ref UInt32 a,
ref UInt32 b,
ref UInt32 c,
ref UInt32 d
ref uint a,
ref uint b,
ref uint c,
ref uint d
)
{
a += b + m[sigma[2 * i]];
@@ -157,16 +148,22 @@ internal partial class RarBLAKE2spStream : RarStream
b = (b >> 7) | (b << 25);
}
internal void Compress(BLAKE2S hash)
private static void Compress(BLAKE2S hash)
{
var m = new UInt32[16];
var v = new UInt32[16];
for (var i = 0; i < 16; i++)
Span<uint> m = stackalloc uint[16];
if (BitConverter.IsLittleEndian)
{
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
MemoryMarshal.Cast<byte, uint>(hash.b).CopyTo(m);
}
else
{
for (var i = 0; i < 16; i++)
{
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
}
}
Span<uint> v = stackalloc uint[16];
for (var i = 0; i < 8; i++)
{
v[i] = hash.h[i];
@@ -184,16 +181,15 @@ internal partial class RarBLAKE2spStream : RarStream
for (var r = 0; r < BLAKE2S_NUM_ROUNDS; r++)
{
ref byte[] sigma = ref k_BLAKE2S_Sigma[r];
G(ref m, ref sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
G(ref m, ref sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
G(ref m, ref sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
G(ref m, ref sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
G(ref m, ref sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
G(ref m, ref sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
G(ref m, ref sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
G(ref m, ref sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
var sigma = k_BLAKE2S_Sigma[r];
G(m, sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
G(m, sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
G(m, sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
G(m, sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
G(m, sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
G(m, sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
G(m, sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
G(m, sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
}
for (var i = 0; i < 8; i++)
@@ -202,103 +198,124 @@ internal partial class RarBLAKE2spStream : RarStream
}
}
internal void Update(BLAKE2S hash, ReadOnlySpan<byte> data, int size)
private static void Update(BLAKE2S hash, ReadOnlySpan<byte> data)
{
var i = 0;
while (size != 0)
while (data.Length != 0)
{
var pos = hash.bufferPosition;
var reminder = BLAKE2S_BLOCK_SIZE - pos;
if (size <= reminder)
var chunkSize = BLAKE2S_BLOCK_SIZE - pos;
if (data.Length <= chunkSize)
{
data.Slice(i, size).CopyTo(new Span<byte>(hash.b, pos, size));
hash.bufferPosition += size;
data.CopyTo(hash.b.AsSpan(pos));
hash.bufferPosition += data.Length;
return;
}
data.Slice(i, reminder).CopyTo(new Span<byte>(hash.b, pos, reminder));
data.Slice(0, chunkSize).CopyTo(hash.b.AsSpan(pos));
hash.t[0] += BLAKE2S_BLOCK_SIZE;
hash.t[1] += hash.t[0] < BLAKE2S_BLOCK_SIZE ? 1U : 0U;
Compress(hash);
hash.bufferPosition = 0;
i += reminder;
size -= reminder;
data = data.Slice(chunkSize);
}
}
internal byte[] Final(BLAKE2S hash)
private static void Final(BLAKE2S hash, Span<byte> output)
{
hash.t[0] += (uint)hash.bufferPosition;
hash.t[1] += hash.t[0] < hash.bufferPosition ? 1U : 0U;
hash.f[0] = BLAKE2S_FINAL_FLAG;
hash.f[1] = hash.lastNodeFlag;
Array.Clear(hash.b, hash.bufferPosition, BLAKE2S_BLOCK_SIZE - hash.bufferPosition);
hash.b.AsSpan(hash.bufferPosition).Clear();
Compress(hash);
var mem = new MemoryStream();
for (var i = 0; i < 8; i++)
if (BitConverter.IsLittleEndian)
{
mem.Write(BitConverter.GetBytes(hash.h[i]), 0, 4);
MemoryMarshal.Cast<uint, byte>(hash.h).CopyTo(output);
}
else
{
for (var i = 0; i < 8; i++)
{
var v = hash.h[i];
output[i * 4] = (byte)v;
output[i * 4 + 1] = (byte)(v >> 8);
output[i * 4 + 2] = (byte)(v >> 16);
output[i * 4 + 3] = (byte)(v >> 24);
}
}
return mem.ToArray();
}
public void ResetCrc()
private static BLAKE2SP CreateBlake2sp()
{
_blake2sp.bufferPosition = 0;
var blake2sp = new BLAKE2SP();
for (UInt32 i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
{
_blake2sp.S[i].bufferPosition = 0;
ResetCrc(_blake2sp.S[i]);
_blake2sp.S[i].h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
_blake2sp.S[i].h[2] ^= i;
_blake2sp.S[i].h[3] ^= (BLAKE2S_DIGEST_SIZE << 24);
var blake2S = blake2sp.S[i];
ResetCrc(blake2S);
var h = blake2S.h;
// word[0]: digest_length | (fanout<<16) | (depth<<24)
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
// word[2]: node_offset = leaf index
h[2] ^= (uint)i;
// word[3]: inner_length in bits 24-31
h[3] ^= BLAKE2S_DIGEST_SIZE << 24;
}
_blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
return blake2sp;
}
internal void Update(BLAKE2SP hash, ReadOnlySpan<byte> data, int size)
private static void Update(BLAKE2SP hash, ReadOnlySpan<byte> data)
{
var i = 0;
var pos = hash.bufferPosition;
while (size != 0)
while (data.Length != 0)
{
var index = pos / BLAKE2S_BLOCK_SIZE;
var reminder = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
if (reminder > size)
var chunkSize = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
if (chunkSize > data.Length)
{
reminder = size;
chunkSize = data.Length;
}
// Update(hash.S[index], data, size);
Update(hash.S[index], data.Slice(i, reminder), reminder);
size -= reminder;
i += reminder;
pos += reminder;
pos &= (BLAKE2S_BLOCK_SIZE * (BLAKE2SP_PARALLEL_DEGREE - 1));
Update(hash.S[index], data.Slice(0, chunkSize));
data = data.Slice(chunkSize);
pos = (pos + chunkSize) & (BLAKE2S_BLOCK_SIZE * BLAKE2SP_PARALLEL_DEGREE - 1);
}
hash.bufferPosition = pos;
}
internal byte[] Final(BLAKE2SP hash)
private static byte[] Final(BLAKE2SP blake2sp)
{
var h = new BLAKE2S();
var blake2s = new BLAKE2S();
ResetCrc(blake2s);
ResetCrc(h);
h.h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
h.h[3] ^= (1 << 16 | BLAKE2S_DIGEST_SIZE << 24);
h.lastNodeFlag = BLAKE2S_FINAL_FLAG;
var h = blake2s.h;
// word[0]: digest_length | (fanout<<16) | (depth<<24) — same as leaves
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
// word[3]: node_depth=1 (bits 16-23), inner_length=32 (bits 24-31)
h[3] ^= (1 << 16) | (BLAKE2S_DIGEST_SIZE << 24);
blake2s.lastNodeFlag = BLAKE2S_FINAL_FLAG;
Span<byte> digest = stackalloc byte[BLAKE2S_DIGEST_SIZE];
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
{
var digest = Final(_blake2sp.S[i]);
Update(h, digest, BLAKE2S_DIGEST_SIZE);
Final(blake2sp.S[i], digest);
Update(blake2s, digest);
}
return Final(h);
Final(blake2s, digest);
return digest.ToArray();
}
private void EnsureHash()
{
if (this._hash == null)
{
this._hash = Final(this._blake2sp!);
// prevent incorrect usage past hash finality by failing fast
this._blake2sp = null;
}
}
public override int Read(byte[] buffer, int offset, int count)
@@ -306,12 +323,12 @@ internal partial class RarBLAKE2spStream : RarStream
var result = base.Read(buffer, offset, count);
if (result != 0)
{
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
Update(this._blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
}
else
{
_hash = Final(_blake2sp);
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
EnsureHash();
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
{
// NOTE: we use the last FileHeader in a multipart volume to check CRC
throw new InvalidFormatException("file crc mismatch");

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Compressors.RLE90;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Squeezed;
@@ -54,14 +55,14 @@ public partial class SqueezeStream
if (bytesRead != 2)
{
return new MemoryStream(Array.Empty<byte>());
return new PooledMemoryStream();
}
int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8);
if (numnodes >= NUMVALS || numnodes == 0)
{
return new MemoryStream(Array.Empty<byte>());
return new PooledMemoryStream();
}
var dnode = new int[numnodes, 2];
@@ -82,7 +83,7 @@ public partial class SqueezeStream
}
var bitReader = new BitReader(_stream);
var huffmanDecoded = new MemoryStream();
var huffmanDecoded = new PooledMemoryStream();
int i = 0;
while (true)

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Text;
using SharpCompress.Common;
using SharpCompress.Compressors.RLE90;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Squeezed;
@@ -67,7 +68,7 @@ public partial class SqueezeStream : Stream
if (numnodes >= NUMVALS || numnodes == 0)
{
return new MemoryStream(Array.Empty<byte>());
return new PooledMemoryStream();
}
var dnode = new int[numnodes, 2];
@@ -78,7 +79,7 @@ public partial class SqueezeStream : Stream
}
var bitReader = new BitReader(_stream);
var huffmanDecoded = new MemoryStream();
var huffmanDecoded = new PooledMemoryStream();
int i = 0;
while (true)

View File

@@ -5,7 +5,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
#if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
#endif
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
using System.Runtime.Intrinsics.Arm;
#endif
@@ -554,7 +554,7 @@ public static unsafe partial class Methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ZSTD_copy16(void* dst, void* src)
{
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
if (AdvSimd.IsSupported)
{
AdvSimd.Store((byte*)dst, AdvSimd.LoadVector128((byte*)src));

View File

@@ -6,7 +6,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
using System.Runtime.Intrinsics.Arm;
#endif
@@ -1172,7 +1172,7 @@ public static unsafe partial class Methods
{
assert(rowEntries == 16 || rowEntries == 32 || rowEntries == 64);
assert(rowEntries <= 64);
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
{
if (rowEntries == 16)
@@ -1272,7 +1272,7 @@ public static unsafe partial class Methods
}
#endif
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
{
if (rowEntries == 16)

View File

@@ -23,12 +23,12 @@ public class AceFactory : Factory, IReaderFactory
yield return "ace";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
AceHeader.IsArchive(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => AceHeader.IsArchiveAsync(stream, cancellationToken);

View File

@@ -25,7 +25,7 @@ public class ArcFactory : Factory, IReaderFactory
yield return "arc";
}
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
//You may have to use some(paranoid) checks to ensure that you actually are
//processing an ARC file, since other archivers also adopted the idea of putting
@@ -63,7 +63,7 @@ public class ArcFactory : Factory, IReaderFactory
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{

View File

@@ -23,12 +23,12 @@ public class ArjFactory : Factory, IReaderFactory
yield return "arj";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
ArjHeader.IsArchive(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => ArjHeader.IsArchiveAsync(stream, cancellationToken);

View File

@@ -54,11 +54,10 @@ public abstract class Factory : IFactory
public abstract IEnumerable<string> GetSupportedExtensions();
/// <inheritdoc/>
public abstract bool IsArchive(Stream stream, string? password = null);
public abstract bool IsArchive(Stream stream, ReaderOptions readerOptions);
public abstract ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
);
@@ -86,7 +85,7 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
stream.Rewind();
if (IsArchive(stream, options.Password))
if (IsArchive(stream, options))
{
stream.Rewind(true);
reader = readerFactory.OpenReader(stream, options);
@@ -106,10 +105,7 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
stream.Rewind();
if (
await IsArchiveAsync(stream, options.Password, cancellationToken)
.ConfigureAwait(false)
)
if (await IsArchiveAsync(stream, options, cancellationToken).ConfigureAwait(false))
{
stream.Rewind(true);
return await readerFactory

View File

@@ -27,7 +27,7 @@ public class GZipFactory
IMultiArchiveFactory,
IReaderFactory,
IWriterFactory,
IWriteableArchiveFactory<GZipWriterOptions>
IWritableArchiveFactory<GZipWriterOptions>
{
#region IFactory
@@ -44,13 +44,13 @@ public class GZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
GZipArchive.IsGZipFile(stream);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => GZipArchive.IsGZipFileAsync(stream, cancellationToken);
@@ -218,7 +218,7 @@ public class GZipFactory
#endregion
#region IWriteableArchiveFactory
#region IWritableArchiveFactory
/// <inheritdoc/>
public IWritableArchive<GZipWriterOptions> CreateArchive() => GZipArchive.CreateArchive();

View File

@@ -37,18 +37,18 @@ public interface IFactory
/// Returns true if the stream represents an archive of the format defined by this type.
/// </summary>
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
/// <param name="password">optional password</param>
bool IsArchive(Stream stream, string? password = null);
/// <param name="readerOptions">Options controlling archive detection.</param>
bool IsArchive(Stream stream, ReaderOptions readerOptions);
/// <summary>
/// Returns true if the stream represents an archive of the format defined by this type asynchronously.
/// </summary>
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
/// <param name="password">optional password</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">cancellation token</param>
ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
);

View File

@@ -32,13 +32,13 @@ public class LzwFactory : Factory, IReaderFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
LzwStream.IsLzwStream(stream);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => LzwStream.IsLzwStreamAsync(stream, cancellationToken);

View File

@@ -31,15 +31,15 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
RarArchive.IsRarFile(stream);
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
RarArchive.IsRarFile(stream, readerOptions);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => RarArchive.IsRarFileAsync(stream, cancellationToken: cancellationToken);
) => RarArchive.IsRarFileAsync(stream, readerOptions, cancellationToken);
/// <inheritdoc/>
public override FileInfo? GetFilePart(int index, FileInfo part1) =>

View File

@@ -34,15 +34,15 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory, I
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
SevenZipArchive.IsSevenZipFile(stream);
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
SevenZipArchive.IsSevenZipFile(stream, readerOptions);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => SevenZipArchive.IsSevenZipFileAsync(stream, cancellationToken);
) => SevenZipArchive.IsSevenZipFileAsync(stream, readerOptions, cancellationToken);
#endregion

View File

@@ -25,7 +25,7 @@ public class TarFactory
IMultiArchiveFactory,
IReaderFactory,
IWriterFactory,
IWriteableArchiveFactory<TarWriterOptions>
IWritableArchiveFactory<TarWriterOptions>
{
#region IFactory
@@ -48,8 +48,9 @@ public class TarFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
var providers = readerOptions.Providers;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)
@@ -76,10 +77,11 @@ public class TarFactory
/// <inheritdoc/>
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{
var providers = readerOptions.Providers;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)
@@ -311,7 +313,7 @@ public class TarFactory
/// <inheritdoc/>
public IReader OpenReader(Stream stream, ReaderOptions? options)
{
options ??= new ReaderOptions();
options ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)
@@ -343,7 +345,7 @@ public class TarFactory
)
{
cancellationToken.ThrowIfCancellationRequested();
options ??= new ReaderOptions();
options ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)
@@ -469,7 +471,7 @@ public class TarFactory
#endregion
#region IWriteableArchiveFactory
#region IWritableArchiveFactory
/// <inheritdoc/>
public IWritableArchive<TarWriterOptions> CreateArchive() => TarArchive.CreateArchive();

View File

@@ -21,12 +21,12 @@ internal class ZStandardFactory : Factory
yield return "zstd";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
ZStandardStream.IsZStandard(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => ZStandardStream.IsZStandardAsync(stream, cancellationToken);
}

View File

@@ -24,7 +24,7 @@ public class ZipFactory
IMultiArchiveFactory,
IReaderFactory,
IWriterFactory,
IWriteableArchiveFactory<ZipWriterOptions>
IWritableArchiveFactory<ZipWriterOptions>
{
#region IFactory
@@ -43,10 +43,10 @@ public class ZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
var startPosition = stream.CanSeek ? stream.Position : -1;
if (ZipArchive.IsZipFile(stream, password))
if (ZipArchive.IsZipFile(stream, readerOptions.Password))
{
return true;
}
@@ -61,7 +61,7 @@ public class ZipFactory
stream.Position = startPosition;
//test the zip (last) file of a multipart zip
if (ZipArchive.IsZipMulti(stream, password))
if (ZipArchive.IsZipMulti(stream, readerOptions.Password))
{
return true;
}
@@ -74,7 +74,7 @@ public class ZipFactory
/// <inheritdoc/>
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{
@@ -84,7 +84,7 @@ public class ZipFactory
// probe for single volume zip
if (
await ZipArchive
.IsZipFileAsync(stream, password, cancellationToken)
.IsZipFileAsync(stream, readerOptions.Password, cancellationToken)
.ConfigureAwait(false)
)
{
@@ -102,7 +102,7 @@ public class ZipFactory
//test the zip (last) file of a multipart zip
if (
await ZipArchive
.IsZipMultiAsync(stream, password, cancellationToken)
.IsZipMultiAsync(stream, readerOptions.Password, cancellationToken)
.ConfigureAwait(false)
)
{
@@ -246,7 +246,7 @@ public class ZipFactory
#endregion
#region IWriteableArchiveFactory
#region IWritableArchiveFactory
/// <inheritdoc/>
public IWritableArchive<ZipWriterOptions> CreateArchive() => ZipArchive.CreateArchive();

View File

@@ -0,0 +1,701 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.IO;
/// <summary>
/// MemoryStream implementation backed by pooled byte arrays.
/// Uses <see cref="ArrayPool{T}"/> to reduce GC pressure for temporary buffers.
/// </summary>
/// <remarks>
/// This implementation is not thread-safe. Use appropriate synchronization for concurrent access.
/// Buffers exposed via <see cref="GetBuffer"/> or <see cref="TryGetBuffer"/> are allocated as
/// fresh non-pooled arrays to avoid exposing pooled memory.
/// </remarks>
public sealed class PooledMemoryStream : MemoryStream
{
private const int MaxStreamLength = int.MaxValue;
private readonly ArrayPool<byte> _arrayPool;
private readonly int _blockSize;
private List<byte[]>? _blocks;
private bool _isOpen;
private int _position;
private int _length;
private int _capacity;
public PooledMemoryStream()
: this(0) { }
public PooledMemoryStream(int capacity)
: this(capacity, Constants.BufferSize, ArrayPool<byte>.Shared) { }
public PooledMemoryStream(int capacity, int blockSize)
: this(capacity, blockSize, ArrayPool<byte>.Shared) { }
public PooledMemoryStream(int capacity, int blockSize, ArrayPool<byte> arrayPool)
{
ThrowHelper.ThrowIfNull(arrayPool, nameof(arrayPool));
ThrowHelper.ThrowIfNegative(capacity, nameof(capacity));
ThrowHelper.ThrowIfNegativeOrZero(blockSize, nameof(blockSize));
_arrayPool = arrayPool;
_blockSize = blockSize;
_blocks = new List<byte[]>();
_isOpen = true;
_position = 0;
_length = 0;
_capacity = capacity;
EnsureSegmentedAllocated(capacity);
}
public override bool CanRead => _isOpen;
public override bool CanSeek => _isOpen;
public override bool CanWrite => _isOpen;
public override long Length
{
get
{
EnsureNotClosed();
return _length;
}
}
public override long Position
{
get
{
EnsureNotClosed();
return _position;
}
set
{
EnsureNotClosed();
ThrowHelper.ThrowIfNegative(value, nameof(value));
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
_position = (int)value;
}
}
public override int Capacity
{
get
{
EnsureNotClosed();
return _capacity;
}
set
{
ThrowHelper.ThrowIfLessThan(value, _length, nameof(value));
EnsureNotClosed();
var target = value;
if (target == _capacity)
{
return;
}
SetCapacityAbsolute(target);
}
}
public override void Flush()
{
EnsureNotClosed();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
EnsureNotClosed();
return Task.CompletedTask;
}
public override long Seek(long offset, SeekOrigin loc)
{
EnsureNotClosed();
var anchor = loc switch
{
SeekOrigin.Begin => 0,
SeekOrigin.Current => _position,
SeekOrigin.End => _length,
_ => throw new ArgumentException("Invalid seek origin.", nameof(loc)),
};
var target = anchor + offset;
if (target < 0)
{
throw new IOException("Attempted to seek before the beginning of the stream.");
}
if (target > MaxStreamLength)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
_position = (int)target;
return _position;
}
public override void SetLength(long value)
{
EnsureWritable();
ThrowHelper.ThrowIfNegative(value, nameof(value));
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
var newLength = (int)value;
if (newLength > _capacity)
{
EnsureCapacityForAppend(newLength);
}
if (newLength > _length)
{
ClearRange(_length, newLength - _length);
}
_length = newLength;
if (_position > newLength)
{
_position = newLength;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
ValidateReadWriteBufferArguments(buffer, offset, count);
EnsureNotClosed();
var available = _length - _position;
if (available <= 0)
{
return 0;
}
if (count > available)
{
count = available;
}
CopyFromSegmented(_position, buffer, offset, count);
_position += count;
return count;
}
public override int ReadByte()
{
EnsureNotClosed();
if (_position >= _length)
{
return -1;
}
var blockIndex = _position / _blockSize;
var blockOffset = _position % _blockSize;
var value = _blocks![blockIndex][blockOffset];
_position++;
return value;
}
public override void Write(byte[] buffer, int offset, int count)
{
ValidateReadWriteBufferArguments(buffer, offset, count);
EnsureWritable();
if (count == 0)
{
return;
}
var endPosition = _position + count;
if (endPosition < 0)
{
throw new IOException("Stream is too long.");
}
if (endPosition > _capacity)
{
EnsureCapacityForAppend(endPosition);
}
if (_position > _length)
{
ClearRange(_length, _position - _length);
}
CopyToSegmented(_position, buffer, offset, count);
_position = endPosition;
if (_position > _length)
{
_length = _position;
}
}
public override void WriteByte(byte value)
{
EnsureWritable();
var endPosition = _position + 1;
if (endPosition < 0)
{
throw new IOException("Stream is too long.");
}
if (endPosition > _capacity)
{
EnsureCapacityForAppend(endPosition);
}
if (_position > _length)
{
ClearRange(_length, _position - _length);
}
var blockIndex = _position / _blockSize;
var blockOffset = _position % _blockSize;
_blocks![blockIndex][blockOffset] = value;
_position = endPosition;
if (_position > _length)
{
_length = _position;
}
}
private byte[] CreateExposableBuffer()
{
var exposable = new byte[_capacity];
if (_length == 0)
{
return exposable;
}
CopyFromSegmented(0, exposable, 0, _length);
return exposable;
}
public override byte[] GetBuffer()
{
EnsureNotClosed();
return CreateExposableBuffer();
}
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
{
EnsureNotClosed();
var exposableBuffer = CreateExposableBuffer();
buffer = new ArraySegment<byte>(exposableBuffer, 0, _length);
return true;
}
public override byte[] ToArray()
{
EnsureNotClosed();
var count = _length;
if (count == 0)
{
return Array.Empty<byte>();
}
var copy = new byte[count];
CopyFromSegmented(0, copy, 0, count);
return copy;
}
public override void WriteTo(Stream stream)
{
ThrowHelper.ThrowIfNull(stream, nameof(stream));
EnsureNotClosed();
var count = _length;
if (count == 0)
{
return;
}
var position = 0;
var remaining = count;
while (remaining > 0)
{
var blockIndex = position / _blockSize;
var blockOffset = position % _blockSize;
var toWrite = Math.Min(remaining, _blockSize - blockOffset);
stream.Write(_blocks![blockIndex], blockOffset, toWrite);
position += toWrite;
remaining -= toWrite;
}
}
public override Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
return Task.FromResult(Read(buffer, offset, count));
}
public override Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
Write(buffer, offset, count);
return Task.CompletedTask;
}
#if !LEGACY_DOTNET
public override int Read(Span<byte> buffer)
{
EnsureNotClosed();
var available = _length - _position;
if (available <= 0)
{
return 0;
}
var count = Math.Min(available, buffer.Length);
var sourcePosition = _position;
var destinationOffset = 0;
var remaining = count;
while (remaining > 0)
{
var blockIndex = sourcePosition / _blockSize;
var blockOffset = sourcePosition % _blockSize;
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
_blocks!
[blockIndex]
.AsSpan(blockOffset, toCopy)
.CopyTo(buffer.Slice(destinationOffset, toCopy));
sourcePosition += toCopy;
destinationOffset += toCopy;
remaining -= toCopy;
}
_position += count;
return count;
}
public override void Write(ReadOnlySpan<byte> buffer)
{
EnsureWritable();
if (buffer.Length == 0)
{
return;
}
var endPosition = _position + buffer.Length;
if (endPosition < 0)
{
throw new IOException("Stream is too long.");
}
if (endPosition > _capacity)
{
EnsureCapacityForAppend(endPosition);
}
if (_position > _length)
{
ClearRange(_length, _position - _length);
}
var sourceOffset = 0;
var destinationPosition = _position;
var remaining = buffer.Length;
while (remaining > 0)
{
var blockIndex = destinationPosition / _blockSize;
var blockOffset = destinationPosition % _blockSize;
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
buffer
.Slice(sourceOffset, toCopy)
.CopyTo(_blocks![blockIndex].AsSpan(blockOffset, toCopy));
sourceOffset += toCopy;
destinationPosition += toCopy;
remaining -= toCopy;
}
_position = endPosition;
if (_position > _length)
{
_length = _position;
}
}
public override ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}
return ValueTask.FromResult(Read(buffer.Span));
}
public override ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default
)
{
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled(cancellationToken);
}
Write(buffer.Span);
return ValueTask.CompletedTask;
}
#endif
protected override void Dispose(bool disposing)
{
if (_isOpen)
{
_isOpen = false;
if (disposing)
{
ReturnPooledBuffers();
}
}
base.Dispose(disposing);
}
private void EnsureNotClosed()
{
if (!_isOpen)
{
throw new ObjectDisposedException(nameof(PooledMemoryStream));
}
}
private void EnsureWritable()
{
EnsureNotClosed();
}
private void EnsureCapacityForAppend(int requiredLength)
{
if (requiredLength < 0)
{
throw new IOException("Stream is too long.");
}
if (requiredLength <= _capacity)
{
return;
}
var nextCapacity = RoundUpToBlockBoundary(requiredLength);
SetCapacityAbsolute(nextCapacity);
}
private void SetCapacityAbsolute(int newCapacity)
{
ThrowHelper.ThrowIfLessThan(newCapacity, _length, nameof(newCapacity));
EnsureSegmentedAllocated(newCapacity);
_capacity = newCapacity;
if (_length > _capacity)
{
_length = _capacity;
}
if (_position > _capacity)
{
_position = _capacity;
}
}
private void EnsureSegmentedAllocated(int capacity)
{
var requiredAllocated = RoundUpToBlockBoundary(capacity);
var requiredBlocks = requiredAllocated == 0 ? 0 : requiredAllocated / _blockSize;
_blocks ??= new List<byte[]>();
while (_blocks.Count < requiredBlocks)
{
_blocks.Add(_arrayPool.Rent(_blockSize));
}
while (_blocks.Count > requiredBlocks)
{
var index = _blocks.Count - 1;
var block = _blocks[index];
_blocks.RemoveAt(index);
_arrayPool.Return(block);
}
}
private int RoundUpToBlockBoundary(int value)
{
if (value <= 0)
{
return 0;
}
var rounded = ((long)value + _blockSize - 1) / _blockSize * _blockSize;
if (rounded > MaxStreamLength)
{
throw new IOException("Stream is too long.");
}
return (int)rounded;
}
private void ClearRange(int absoluteStart, int count)
{
if (count <= 0)
{
return;
}
var position = absoluteStart;
var remaining = count;
while (remaining > 0)
{
var blockIndex = position / _blockSize;
var blockOffset = position % _blockSize;
var toClear = Math.Min(remaining, _blockSize - blockOffset);
Array.Clear(_blocks![blockIndex], blockOffset, toClear);
position += toClear;
remaining -= toClear;
}
}
private void CopyFromSegmented(
int absoluteSourcePosition,
byte[] destination,
int offset,
int count
)
{
var sourcePosition = absoluteSourcePosition;
var destinationOffset = offset;
var remaining = count;
while (remaining > 0)
{
var blockIndex = sourcePosition / _blockSize;
var blockOffset = sourcePosition % _blockSize;
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
Buffer.BlockCopy(
_blocks![blockIndex],
blockOffset,
destination,
destinationOffset,
toCopy
);
sourcePosition += toCopy;
destinationOffset += toCopy;
remaining -= toCopy;
}
}
private void CopyToSegmented(
int absoluteDestinationPosition,
byte[] source,
int offset,
int count
)
{
var sourceOffset = offset;
var destinationPosition = absoluteDestinationPosition;
var remaining = count;
while (remaining > 0)
{
var blockIndex = destinationPosition / _blockSize;
var blockOffset = destinationPosition % _blockSize;
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
Buffer.BlockCopy(source, sourceOffset, _blocks![blockIndex], blockOffset, toCopy);
sourceOffset += toCopy;
destinationPosition += toCopy;
remaining -= toCopy;
}
}
private void ReturnSegmentedBlocks()
{
if (_blocks is null)
{
return;
}
for (var i = 0; i < _blocks.Count; i++)
{
_arrayPool.Return(_blocks[i]);
}
_blocks.Clear();
}
private void ReturnPooledBuffers()
{
ReturnSegmentedBlocks();
_blocks = null;
}
private static void ValidateReadWriteBufferArguments(byte[] buffer, int offset, int count)
{
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
ThrowHelper.ThrowIfNegative(offset, nameof(offset));
ThrowHelper.ThrowIfNegative(count, nameof(count));
if (buffer.Length - offset < count)
{
throw new ArgumentException("Offset and length are out of bounds.");
}
}
}

View File

@@ -203,11 +203,17 @@ public partial class SharpCompressStream : Stream, IStreamStack
// Allocate ring buffer with the requested minimum size (at least the global default).
if (_ringBuffer is null)
{
var size =
var requiredSize =
minBufferSize.GetValueOrDefault() > Constants.RewindableBufferSize
? minBufferSize.GetValueOrDefault()
: Constants.RewindableBufferSize;
_ringBuffer = new RingBuffer(size);
_ringBuffer = new RingBuffer(requiredSize);
}
else if (minBufferSize.HasValue && minBufferSize.Value > _ringBuffer.Capacity)
{
throw new ArchiveOperationException(
$"StartRecording requires a ring buffer of at least {minBufferSize.Value} bytes, but the stream was created with capacity {_ringBuffer.Capacity}."
);
}
// Mark current position as recording anchor

View File

@@ -28,7 +28,7 @@ public static class StreamExtensions
public async ValueTask SkipAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
await stream.CopyToAsync(Stream.Null, cancellationToken).ConfigureAwait(false);
#else
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);

View File

@@ -22,20 +22,16 @@ public abstract partial class AbstractReader<TEntry, TVolume> : IReader, IAsyncR
private bool _wroteCurrentEntry;
private readonly bool _disposeVolume;
internal AbstractReader(
ReaderOptions options,
ArchiveType archiveType,
bool disposeVolume = true
)
internal AbstractReader(ReaderOptions options, ArchiveType type, bool disposeVolume = true)
{
ArchiveType = archiveType;
Type = type;
_disposeVolume = disposeVolume;
Options = options;
}
internal ReaderOptions Options { get; }
public ArchiveType ArchiveType { get; }
public ArchiveType Type { get; }
/// <summary>
/// Current volume that the current entry resides in

View File

@@ -19,8 +19,8 @@ public partial class AceReader
/// <returns>An AceReader instance.</returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new SingleVolumeAceReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new SingleVolumeAceReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
/// <summary>
@@ -31,19 +31,19 @@ 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 ?? new ReaderOptions());
var streamArray = streams.RequireReadable();
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
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 ?? new ReaderOptions());
var streamArray = streams.RequireReadable();
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -84,6 +84,7 @@ public partial class AceReader
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
}

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

@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arc;
public partial class ArcReader : IReaderOpenable
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -48,6 +48,7 @@ public partial class ArcReader : IReaderOpenable
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
}

View File

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

View File

@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arj;
public partial class ArjReader : IReaderOpenable
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -48,6 +48,7 @@ public partial class ArjReader : IReaderOpenable
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
}

View File

@@ -31,8 +31,8 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new SingleVolumeArjReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new SingleVolumeArjReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
/// <summary>
@@ -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 ?? new ReaderOptions());
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

@@ -10,14 +10,14 @@ public partial class GZipReader
#endif
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -49,12 +49,13 @@ public partial class GZipReader
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new GZipReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new GZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
}

View File

@@ -8,7 +8,7 @@ namespace SharpCompress.Readers;
public interface IAsyncReader : IAsyncDisposable
{
ArchiveType ArchiveType { get; }
ArchiveType Type { get; }
IEntry Entry { get; }

View File

@@ -6,7 +6,7 @@ namespace SharpCompress.Readers;
public interface IReader : IDisposable
{
ArchiveType ArchiveType { get; }
ArchiveType Type { get; }
IEntry Entry { get; }

View File

@@ -24,6 +24,6 @@ public interface IReaderFactory : Factories.IFactory
ValueTask<IAsyncReader> OpenAsyncReader(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken
CancellationToken cancellationToken = default
);
}

View File

@@ -17,7 +17,7 @@ public interface IReaderOpenable
public static abstract IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null);
public static abstract ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);

View File

@@ -10,14 +10,14 @@ public partial class LzwReader
#endif
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -49,12 +49,13 @@ public partial class LzwReader
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new LzwReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new LzwReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
}

View File

@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Rar;
public partial class RarReader : IReaderOpenable
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(

View File

@@ -49,7 +49,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
readerOptions ??= new ReaderOptions { LeaveStreamOpen = false };
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
@@ -60,7 +60,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
public static IReader OpenReader(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfos.Select(x => x.OpenRead()), options);
}
@@ -72,8 +72,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new SingleVolumeRarReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new SingleVolumeRarReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
/// <summary>
@@ -84,8 +84,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 ?? new ReaderOptions());
var streamArray = streams.RequireReadable();
return new MultiVolumeRarReader(streamArray, options ?? ReaderOptions.ForExternalStream);
}
protected override IEnumerable<RarReaderEntry> GetEntries(Stream stream)

View File

@@ -25,7 +25,11 @@ public static partial class ReaderFactory
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken);
return OpenAsyncReader(
new FileInfo(filePath),
options ?? ReaderOptions.ForFilePath,
cancellationToken
);
}
/// <summary>
@@ -52,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

@@ -12,7 +12,7 @@ public static partial class ReaderFactory
public static IReader OpenReader(string filePath, ReaderOptions? options = null)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenReader(new FileInfo(filePath), options);
return OpenReader(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
}
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null)
@@ -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

@@ -24,9 +24,36 @@ namespace SharpCompress.Readers;
public sealed record ReaderOptions : IReaderOptions
{
/// <summary>
/// SharpCompress will keep the supplied streams open. Default is true.
/// Whether SharpCompress leaves the supplied streams open when the reader/archive is disposed.
/// As of v0.21, the library is documented to close streams by default; this option now defaults to false.
/// Set to true when passing caller-owned streams that should not be disposed.
/// </summary>
public bool LeaveStreamOpen { get; init; } = true;
/// <remarks>
/// <para>
/// <b>Default behavior (LeaveStreamOpen = false):</b>
/// When you open an archive from a file path (e.g., <c>GZipArchive.OpenArchive(filePath)</c>),
/// SharpCompress manages the stream lifetime and closes it on Dispose.
/// </para>
/// <para>
/// <b>Caller-provided streams (LeaveStreamOpen = true):</b>
/// When you pass a stream you created (FileStream, MemoryStream, NetworkStream, etc.),
/// set LeaveStreamOpen = true to prevent SharpCompress from disposing it.
/// Use <see cref="ForExternalStream"/> preset for convenience.
/// </para>
/// <para>
/// <b>Example:</b>
/// <code>
/// // File-based: stream managed by library
/// using var archive = GZipArchive.OpenArchive(filePath); // LeaveStreamOpen = false
///
/// // Caller-provided stream: caller manages lifetime
/// using var stream = File.OpenRead(filePath);
/// var options = new ReaderOptions { LeaveStreamOpen = true };
/// using var archive = GZipArchive.OpenArchive(stream, options);
/// </code>
/// </para>
/// </remarks>
public bool LeaveStreamOpen { get; init; } = false;
/// <summary>
/// Encoding to use for archive entry names.
@@ -124,35 +151,34 @@ public sealed record ReaderOptions : IReaderOptions
/// <summary>
/// Gets ReaderOptions configured for caller-provided streams.
/// </summary>
public static ReaderOptions ForExternalStream => new() { LeaveStreamOpen = true };
internal static ReaderOptions Default => new();
public static ReaderOptions ForExternalStream => Default.WithLeaveStreamOpen(true);
/// <summary>
/// Gets ReaderOptions configured for file-based overloads that open their own stream.
/// </summary>
public static ReaderOptions ForFilePath => new() { LeaveStreamOpen = false };
public static ReaderOptions ForFilePath => Default;
/// <summary>
/// Creates ReaderOptions for reading encrypted archives.
/// </summary>
/// <param name="password">The password for encrypted archives.</param>
public static ReaderOptions ForEncryptedArchive(string? password = null) =>
new ReaderOptions().WithPassword(password);
Default.WithPassword(password);
/// <summary>
/// Creates ReaderOptions for archives with custom character encoding.
/// </summary>
/// <param name="encoding">The encoding for archive entry names.</param>
public static ReaderOptions ForEncoding(IArchiveEncoding encoding) =>
new ReaderOptions().WithArchiveEncoding(encoding);
Default.WithArchiveEncoding(encoding);
/// <summary>
/// Creates ReaderOptions for self-extracting archives that require header search.
/// </summary>
public static ReaderOptions ForSelfExtractingArchive(string? password = null) =>
new ReaderOptions()
.WithLookForHeader(true)
.WithPassword(password)
.WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
Default.WithLookForHeader(true).WithPassword(password).WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
// Note: Parameterized constructors have been removed.
// Use fluent With*() helpers or object initializers instead:

View File

@@ -72,13 +72,13 @@ public partial class TarReader
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
path.NotNullOrEmpty(nameof(path));
return OpenAsyncReader(new FileInfo(path), readerOptions, cancellationToken);
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncReader(new FileInfo(filePath), readerOptions, cancellationToken);
}
public static async ValueTask<IAsyncReader> OpenAsyncReader(
@@ -89,7 +89,7 @@ public partial class TarReader
{
cancellationToken.ThrowIfCancellationRequested();
stream.NotNull(nameof(stream));
readerOptions ??= new ReaderOptions();
readerOptions ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = SharpCompressStream.Create(
stream,
bufferSize: Math.Max(
@@ -143,7 +143,7 @@ public partial class TarReader
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
readerOptions ??= ReaderOptions.ForFilePath;
var stream = fileInfo.OpenAsyncReadStream(cancellationToken);
return await OpenAsyncReader(stream, readerOptions, cancellationToken)
.ConfigureAwait(false);
@@ -158,7 +158,7 @@ public partial class TarReader
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
@@ -170,8 +170,8 @@ public partial class TarReader
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
readerOptions ??= new ReaderOptions();
stream.RequireReadable();
readerOptions ??= ReaderOptions.ForExternalStream;
var sharpCompressStream = SharpCompressStream.Create(
stream,
bufferSize: Math.Max(

View File

@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Zip;
public partial class ZipReader : IReaderOpenable
{
public static ValueTask<IAsyncReader> OpenAsyncReader(
string path,
string filePath,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
filePath.NotNullOrEmpty(nameof(filePath));
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
@@ -48,6 +48,7 @@ public partial class ZipReader : IReaderOpenable
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= ReaderOptions.ForFilePath;
return OpenReader(fileInfo.OpenRead(), readerOptions);
}
}

View File

@@ -47,8 +47,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
/// <returns></returns>
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
return new ZipReader(stream, readerOptions ?? new ReaderOptions());
stream.RequireReadable();
return new ZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
}
public static IReader OpenReader(
@@ -57,8 +57,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
IEnumerable<ZipEntry> entries
)
{
stream.NotNull(nameof(stream));
return new ZipReader(stream, options ?? new ReaderOptions(), entries);
stream.RequireReadable();
return new ZipReader(stream, options ?? ReaderOptions.ForExternalStream, entries);
}
#endregion Open

View File

@@ -6,7 +6,7 @@
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
<Authors>Adam Hathcock</Authors>
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net10.0</TargetFrameworks>
<AssemblyName>SharpCompress</AssemblyName>
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SharpCompress;
internal static class StreamValidationExtensions
{
internal static void RequireReadable(this Stream stream)
{
stream.NotNull(nameof(stream));
if (!stream.CanRead)
{
throw new ArgumentException("Stream must be readable", nameof(stream));
}
}
internal static void RequireSeekable(this Stream stream)
{
stream.NotNull(nameof(stream));
if (!stream.CanSeek)
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
}
internal static void RequireWritable(this Stream stream)
{
stream.NotNull(nameof(stream));
if (!stream.CanWrite)
{
throw new ArgumentException("Stream must be writable", nameof(stream));
}
}
internal static IEnumerable<Stream> RequireSeekable(this IEnumerable<Stream> streams)
{
foreach (var stream in streams)
{
stream.RequireSeekable();
yield return stream;
}
}
internal static IEnumerable<Stream> RequireReadable(this IEnumerable<Stream> streams)
{
foreach (var stream in streams)
{
stream.RequireReadable();
yield return stream;
}
}
}

View File

@@ -60,6 +60,15 @@ internal static class ThrowHelper
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfGreaterThan(long value, long other, string? paramName = null)
{
if (value > other)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfGreaterThan(uint value, uint other, string? paramName = null)
{

View File

@@ -20,7 +20,7 @@ public abstract partial class AbstractWriter(ArchiveType type, IWriterOptions wr
protected Stream? OutputStream { get; private set; }
public ArchiveType WriterType { get; } = type;
public ArchiveType Type { get; } = type;
protected IWriterOptions WriterOptions { get; } = writerOptions;

View File

@@ -1,5 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.Writers.GZip;
@@ -15,28 +17,43 @@ public partial class GZipWriter : IWriterOpenable<GZipWriterOptions>
public static IWriter OpenWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
{
fileInfo.NotNull(nameof(fileInfo));
return new GZipWriter(fileInfo.OpenWrite(), writerOptions);
return new GZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
}
public static IWriter OpenWriter(Stream stream, GZipWriterOptions writerOptions)
{
stream.NotNull(nameof(stream));
stream.RequireWritable();
return new GZipWriter(stream, writerOptions);
}
public static IAsyncWriter OpenAsyncWriter(string stream, GZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
GZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(Stream stream, GZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
GZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
GZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
}
}
#endif

View File

@@ -6,7 +6,7 @@ namespace SharpCompress.Writers;
public interface IWriter : IDisposable
{
ArchiveType WriterType { get; }
ArchiveType Type { get; }
void Write(string filename, Stream source, DateTime? modificationTime);
void WriteDirectory(string directoryName, DateTime? modificationTime);
}

View File

@@ -1,6 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Options;
namespace SharpCompress.Writers;
@@ -17,22 +18,25 @@ public interface IWriterOpenable<TWriterOptions>
/// Opens a Writer asynchronously.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="archiveType">The archive type.</param>
/// <param name="writerOptions">Writer options.</param>
/// <returns>A task that returns an IWriter.</returns>
public static abstract IAsyncWriter OpenAsyncWriter(
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that returns an async writer.</returns>
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
TWriterOptions writerOptions
TWriterOptions writerOptions,
CancellationToken cancellationToken = default
);
public static abstract IAsyncWriter OpenAsyncWriter(
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
TWriterOptions writerOptions
TWriterOptions writerOptions,
CancellationToken cancellationToken = default
);
public static abstract IAsyncWriter OpenAsyncWriter(
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
TWriterOptions writerOptions
TWriterOptions writerOptions,
CancellationToken cancellationToken = default
);
}
#endif

View File

@@ -1,5 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Writers.SevenZip;
@@ -20,7 +22,13 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
public static IWriter OpenWriter(FileInfo fileInfo, SevenZipWriterOptions writerOptions)
{
fileInfo.NotNull(nameof(fileInfo));
return new SevenZipWriter(fileInfo.OpenWrite(), writerOptions);
return new SevenZipWriter(
fileInfo.OpenWrite(),
writerOptions with
{
LeaveStreamOpen = false,
}
);
}
/// <summary>
@@ -28,35 +36,47 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
/// </summary>
public static IWriter OpenWriter(Stream stream, SevenZipWriterOptions writerOptions)
{
stream.NotNull(nameof(stream));
stream.RequireWritable();
return new SevenZipWriter(stream, writerOptions);
}
/// <summary>
/// Opens a new async SevenZipWriter for the specified file path.
/// </summary>
public static IAsyncWriter OpenAsyncWriter(string filePath, SevenZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
SevenZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(filePath, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
}
/// <summary>
/// Opens a new async SevenZipWriter for the specified stream.
/// </summary>
public static IAsyncWriter OpenAsyncWriter(Stream stream, SevenZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
SevenZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
}
/// <summary>
/// Opens a new async SevenZipWriter for the specified file.
/// </summary>
public static IAsyncWriter OpenAsyncWriter(
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
SevenZipWriterOptions writerOptions
SevenZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
}
}
#endif

View File

@@ -203,7 +203,7 @@ public partial class SevenZipWriter : AbstractWriter
var filesInfo = new SevenZipFilesInfoWriter { Entries = entries.ToArray() };
// Write header to a temporary stream first
using var headerStream = new MemoryStream();
using var headerStream = new PooledMemoryStream();
ArchiveHeaderWriter.WriteRawHeader(headerStream, mainStreamsInfo, filesInfo);
// Optionally compress the header
@@ -244,7 +244,7 @@ public partial class SevenZipWriter : AbstractWriter
};
// Write encoded header to a second temporary stream
using var encodedHeaderStream = new MemoryStream();
using var encodedHeaderStream = new PooledMemoryStream();
ArchiveHeaderWriter.WriteEncodedHeader(encodedHeaderStream, headerStreamsInfo);
// Write the encoded header to the output
@@ -252,12 +252,10 @@ public partial class SevenZipWriter : AbstractWriter
encodedHeaderStream.Position = 0;
encodedHeaderStream.CopyTo(output);
// Compute CRC of the encoded header
var headerCrc = Crc32Stream.Compute(
Crc32Stream.DEFAULT_POLYNOMIAL,
Crc32Stream.DEFAULT_SEED,
encodedHeaderStream.GetBuffer().AsSpan(0, (int)encodedHeaderStream.Length)
);
// Compute CRC of the encoded header without allocating a contiguous buffer
var encodedHeaderCrcSink = new Crc32Stream(Stream.Null);
encodedHeaderStream.WriteTo(encodedHeaderCrcSink);
var headerCrc = encodedHeaderCrcSink.Crc;
// Back-patch signature header
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
@@ -283,12 +281,10 @@ public partial class SevenZipWriter : AbstractWriter
rawHeaderStream.Position = 0;
rawHeaderStream.CopyTo(output);
// Compute CRC of the raw header
var headerCrc = Crc32Stream.Compute(
Crc32Stream.DEFAULT_POLYNOMIAL,
Crc32Stream.DEFAULT_SEED,
rawHeaderStream.GetBuffer().AsSpan(0, (int)rawHeaderStream.Length)
);
// Compute CRC of the raw header without allocating a contiguous buffer
var rawHeaderCrcSink = new Crc32Stream(Stream.Null);
rawHeaderStream.WriteTo(rawHeaderCrcSink);
var headerCrc = rawHeaderCrcSink.Crc;
// Back-patch signature header
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);

View File

@@ -1,5 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.Writers.Tar;
@@ -15,28 +17,43 @@ public partial class TarWriter : IWriterOpenable<TarWriterOptions>
public static IWriter OpenWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
{
fileInfo.NotNull(nameof(fileInfo));
return new TarWriter(fileInfo.OpenWrite(), writerOptions);
return new TarWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
}
public static IWriter OpenWriter(Stream stream, TarWriterOptions writerOptions)
{
stream.NotNull(nameof(stream));
stream.RequireWritable();
return new TarWriter(stream, writerOptions);
}
public static IAsyncWriter OpenAsyncWriter(string stream, TarWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
TarWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(Stream stream, TarWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
TarWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
TarWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
}
}
#endif

View File

@@ -75,6 +75,8 @@ public static class WriterFactory
IWriterOptions writerOptions
)
{
stream.RequireWritable();
var factory = Factories
.Factory.Factories.OfType<IWriterFactory>()
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
@@ -102,6 +104,8 @@ public static class WriterFactory
CancellationToken cancellationToken = default
)
{
stream.RequireWritable();
var factory = Factories
.Factory.Factories.OfType<IWriterFactory>()
.FirstOrDefault(item => item.KnownArchiveType == archiveType);

View File

@@ -1,7 +1,6 @@
using System;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Compressors;
using SharpCompress.Providers;
using D = SharpCompress.Compressors.Deflate;
@@ -18,17 +17,10 @@ namespace SharpCompress.Writers;
/// </remarks>
public sealed record WriterOptions : IWriterOptions
{
private CompressionType _compressionType;
private int _compressionLevel;
/// <summary>
/// The compression type to use for the archive.
/// </summary>
public CompressionType CompressionType
{
get => _compressionType;
init => _compressionType = value;
}
public CompressionType CompressionType { get; init; }
/// <summary>
/// The compression level to be used when the compression type supports variable levels.
@@ -40,11 +32,11 @@ public sealed record WriterOptions : IWriterOptions
/// </summary>
public int CompressionLevel
{
get => _compressionLevel;
get;
init
{
CompressionLevelValidation.Validate(CompressionType, value);
_compressionLevel = value;
field = value;
}
}

View File

@@ -1,5 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.Writers.Zip;
@@ -15,28 +17,43 @@ public partial class ZipWriter : IWriterOpenable<ZipWriterOptions>
public static IWriter OpenWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
{
fileInfo.NotNull(nameof(fileInfo));
return new ZipWriter(fileInfo.OpenWrite(), writerOptions);
return new ZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
}
public static IWriter OpenWriter(Stream stream, ZipWriterOptions writerOptions)
{
stream.NotNull(nameof(stream));
stream.RequireWritable();
return new ZipWriter(stream, writerOptions);
}
public static IAsyncWriter OpenAsyncWriter(string stream, ZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
ZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(Stream stream, ZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
ZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(stream, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
}
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
ZipWriterOptions writerOptions,
CancellationToken cancellationToken = default
)
{
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
cancellationToken.ThrowIfCancellationRequested();
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
}
}
#endif

View File

@@ -313,48 +313,6 @@
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
},
".NETCoreApp,Version=v5.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[10.0.102, )",
"resolved": "10.0.102",
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "10.0.102",
"Microsoft.SourceLink.Common": "10.0.102"
}
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
"requested": "[17.14.15, )",
"resolved": "17.14.15",
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
},
"net6.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -397,48 +355,6 @@
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
},
"net7.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[10.0.102, )",
"resolved": "10.0.102",
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "10.0.102",
"Microsoft.SourceLink.Common": "10.0.102"
}
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
"requested": "[17.14.15, )",
"resolved": "17.14.15",
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
},
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
@@ -486,48 +402,6 @@
"resolved": "10.0.102",
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
},
"net9.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[10.0.102, )",
"resolved": "10.0.102",
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "10.0.102",
"Microsoft.SourceLink.Common": "10.0.102"
}
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
"requested": "[17.14.15, )",
"resolved": "17.14.15",
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.102",
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
}
}
}
}