mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-08 18:16:30 +00:00
First pass with zip
This commit is contained in:
@@ -18,11 +18,12 @@ public static class IArchiveEntryExtensions
|
||||
/// <param name="streamToWriteTo">The stream to write the entry content to.</param>
|
||||
/// <param name="progress">Optional progress reporter for tracking extraction progress.</param>
|
||||
public void WriteTo(Stream streamToWriteTo, IProgress<ProgressReport>? progress = null) =>
|
||||
archiveEntry.WriteTo(streamToWriteTo, null, progress);
|
||||
archiveEntry.WriteTo(streamToWriteTo, null, progress: progress);
|
||||
|
||||
private void WriteTo(
|
||||
Stream streamToWriteTo,
|
||||
int? bufferSize,
|
||||
ExtractionOptions? options = null,
|
||||
IProgress<ProgressReport>? progress = null
|
||||
)
|
||||
{
|
||||
@@ -32,7 +33,10 @@ public static class IArchiveEntryExtensions
|
||||
}
|
||||
|
||||
using var entryStream = archiveEntry.OpenEntryStream();
|
||||
var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress);
|
||||
var checkedStream = options is null
|
||||
? entryStream
|
||||
: IEntryExtensions.WrapWithChecksumValidation(archiveEntry, entryStream, options);
|
||||
var sourceStream = WrapWithProgress(checkedStream, archiveEntry, progress);
|
||||
sourceStream.CopyTo(streamToWriteTo, bufferSize ?? Constants.BufferSize);
|
||||
}
|
||||
|
||||
@@ -49,13 +53,19 @@ public static class IArchiveEntryExtensions
|
||||
)
|
||||
{
|
||||
await archiveEntry
|
||||
.WriteToAsync(streamToWriteTo, Constants.BufferSize, progress, cancellationToken)
|
||||
.WriteToAsync(
|
||||
streamToWriteTo,
|
||||
Constants.BufferSize,
|
||||
progress: progress,
|
||||
cancellationToken: cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask WriteToAsync(
|
||||
Stream streamToWriteTo,
|
||||
int? bufferSize,
|
||||
ExtractionOptions? options = null,
|
||||
IProgress<ProgressReport>? progress = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
@@ -74,7 +84,10 @@ public static class IArchiveEntryExtensions
|
||||
.OpenEntryStreamAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
#endif
|
||||
var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress);
|
||||
var checkedStream = options is null
|
||||
? entryStream
|
||||
: IEntryExtensions.WrapWithChecksumValidation(archiveEntry, entryStream, options);
|
||||
var sourceStream = WrapWithProgress(checkedStream, archiveEntry, progress);
|
||||
await sourceStream
|
||||
.CopyToAsync(streamToWriteTo, bufferSize ?? Constants.BufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -154,13 +167,14 @@ public static class IArchiveEntryExtensions
|
||||
/// </summary>
|
||||
public void WriteToFile(string destinationFileName, ExtractionOptions? options = null)
|
||||
{
|
||||
options ??= new ExtractionOptions();
|
||||
entry.WriteEntryToFile(
|
||||
destinationFileName,
|
||||
options,
|
||||
(x, fm) =>
|
||||
{
|
||||
using var fs = File.Open(x, fm);
|
||||
entry.WriteTo(fs, options?.BufferSize ?? Constants.BufferSize);
|
||||
entry.WriteTo(fs, options?.BufferSize ?? Constants.BufferSize, options, null);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -172,7 +186,9 @@ public static class IArchiveEntryExtensions
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
)
|
||||
{
|
||||
options ??= new ExtractionOptions();
|
||||
await entry
|
||||
.WriteEntryToFileAsync(
|
||||
destinationFileName,
|
||||
@@ -181,11 +197,12 @@ public static class IArchiveEntryExtensions
|
||||
{
|
||||
using var fs = File.Open(x, fm);
|
||||
await entry
|
||||
.WriteToAsync(fs, options?.BufferSize, null, ct)
|
||||
.WriteToAsync(fs, options.BufferSize, options, null, ct)
|
||||
.ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/SharpCompress/Common/ChecksumDescriptor.cs
Normal file
12
src/SharpCompress/Common/ChecksumDescriptor.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
internal enum ChecksumKind
|
||||
{
|
||||
Crc32,
|
||||
}
|
||||
|
||||
internal readonly record struct ChecksumDescriptor(
|
||||
ChecksumKind Kind,
|
||||
long ExpectedValue,
|
||||
bool IsAvailable
|
||||
);
|
||||
140
src/SharpCompress/Common/ChecksumValidationStream.cs
Normal file
140
src/SharpCompress/Common/ChecksumValidationStream.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
internal sealed class ChecksumValidationStream : Stream
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly ChecksumDescriptor _checksum;
|
||||
private readonly string _entryName;
|
||||
private readonly uint[] _crc32Table;
|
||||
private uint _seed = Crc32Stream.DEFAULT_SEED;
|
||||
private bool _validated;
|
||||
|
||||
internal ChecksumValidationStream(Stream stream, ChecksumDescriptor checksum, string? entryName)
|
||||
{
|
||||
_stream = stream;
|
||||
_checksum = checksum;
|
||||
_entryName = string.IsNullOrEmpty(entryName) ? "Entry" : entryName!;
|
||||
_crc32Table = Crc32Stream.InitializeTable(Crc32Stream.DEFAULT_POLYNOMIAL);
|
||||
}
|
||||
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => _stream.Flush();
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) =>
|
||||
_stream.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var read = _stream.Read(buffer, offset, count);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
return read;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
var read = _stream.Read(buffer);
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
var value = _stream.ReadByte();
|
||||
if (value == -1)
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
else
|
||||
{
|
||||
_seed = Crc32Stream.CalculateCrc(_crc32Table, _seed, (byte)value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var read = await _stream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
return read;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var read = await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
private void UpdateAndValidateAtEof(ReadOnlySpan<byte> buffer, int read)
|
||||
{
|
||||
if (read > 0)
|
||||
{
|
||||
_seed = Crc32Stream.CalculateCrc(_crc32Table, _seed, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (_validated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_validated = true;
|
||||
|
||||
if (_checksum.Kind != ChecksumKind.Crc32)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var actual = ~_seed;
|
||||
var expected = unchecked((uint)_checksum.ExpectedValue);
|
||||
if (actual != expected)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expected:X8}, actual 0x{actual:X8}."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,8 @@ public abstract class Entry : IEntry
|
||||
|
||||
internal virtual void Close() { }
|
||||
|
||||
internal virtual ChecksumDescriptor Checksum => default;
|
||||
|
||||
/// <summary>
|
||||
/// Entry file attribute.
|
||||
/// </summary>
|
||||
|
||||
@@ -43,6 +43,15 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; } = Constants.BufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// Validate archive entry checksums during extraction when checksum metadata is available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Formats without payload checksums skip this validation. Compression-format integrity
|
||||
/// checks that are required to decode data may still fail even when this is disabled.
|
||||
/// </remarks>
|
||||
public bool CheckCrc { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for writing symbolic links to disk.
|
||||
/// The first parameter is the source path (where the symlink is created).
|
||||
|
||||
@@ -5,6 +5,24 @@ namespace SharpCompress.Common;
|
||||
|
||||
internal static partial class IEntryExtensions
|
||||
{
|
||||
internal static Stream WrapWithChecksumValidation(
|
||||
IEntry entry,
|
||||
Stream source,
|
||||
ExtractionOptions? options
|
||||
)
|
||||
{
|
||||
if (options?.CheckCrc != false && entry is Entry typedEntry)
|
||||
{
|
||||
var checksum = typedEntry.Checksum;
|
||||
if (checksum.IsAvailable)
|
||||
{
|
||||
return new ChecksumValidationStream(source, checksum, entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
extension(IEntry entry)
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -22,6 +22,7 @@ internal partial class DirectoryEntryHeader
|
||||
.ReadUInt16Async()
|
||||
.ConfigureAwait(false);
|
||||
Crc = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
IsCrcAvailable = true;
|
||||
CompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
UncompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
var nameLength = await reader.ReadUInt16Async().ConfigureAwait(false);
|
||||
|
||||
@@ -17,6 +17,7 @@ internal partial class DirectoryEntryHeader : ZipFileEntry
|
||||
OriginalLastModifiedTime = LastModifiedTime = reader.ReadUInt16();
|
||||
OriginalLastModifiedDate = LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
IsCrcAvailable = true;
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
var nameLength = reader.ReadUInt16();
|
||||
|
||||
@@ -20,6 +20,7 @@ internal partial class LocalEntryHeader
|
||||
.ReadUInt16Async()
|
||||
.ConfigureAwait(false);
|
||||
Crc = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
IsCrcAvailable = !Flags.HasFlag(HeaderFlags.UsePostDataDescriptor);
|
||||
CompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
UncompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
var nameLength = await reader.ReadUInt16Async().ConfigureAwait(false);
|
||||
|
||||
@@ -16,6 +16,7 @@ internal partial class LocalEntryHeader : ZipFileEntry
|
||||
OriginalLastModifiedTime = LastModifiedTime = reader.ReadUInt16();
|
||||
OriginalLastModifiedDate = LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
IsCrcAvailable = !Flags.HasFlag(HeaderFlags.UsePostDataDescriptor);
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
var nameLength = reader.ReadUInt16();
|
||||
|
||||
@@ -80,6 +80,8 @@ internal abstract partial class ZipFileEntry(ZipHeaderType type, IArchiveEncodin
|
||||
|
||||
internal uint Crc { get; set; }
|
||||
|
||||
internal bool IsCrcAvailable { get; set; }
|
||||
|
||||
protected void LoadExtra(byte[] extra)
|
||||
{
|
||||
for (var i = 0; i < extra.Length; )
|
||||
|
||||
@@ -159,6 +159,7 @@ internal sealed partial class SeekableZipHeaderFactory : ZipHeaderFactory
|
||||
if (FlagUtility.HasFlag(localEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor))
|
||||
{
|
||||
localEntryHeader.Crc = directoryEntryHeader.Crc;
|
||||
localEntryHeader.IsCrcAvailable = true;
|
||||
localEntryHeader.CompressedSize = directoryEntryHeader.CompressedSize;
|
||||
localEntryHeader.UncompressedSize = directoryEntryHeader.UncompressedSize;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ internal sealed partial class StreamingZipHeaderFactory
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
lastEntryHeader.Crc = crc;
|
||||
lastEntryHeader.IsCrcAvailable = true;
|
||||
|
||||
//attempt 32bit read
|
||||
ulong compressedSize = await _reader
|
||||
@@ -205,6 +206,7 @@ internal sealed partial class StreamingZipHeaderFactory
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
lastEntryHeader.Crc = crc;
|
||||
lastEntryHeader.IsCrcAvailable = true;
|
||||
|
||||
// The DataDescriptor can be either 64bit or 32bit
|
||||
var compressedSize = await _reader
|
||||
@@ -283,6 +285,7 @@ internal sealed partial class StreamingZipHeaderFactory
|
||||
localHeader.UncompressedSize = directoryHeader.Size;
|
||||
localHeader.CompressedSize = directoryHeader.CompressedSize;
|
||||
localHeader.Crc = (uint)directoryHeader.Crc;
|
||||
localHeader.IsCrcAvailable = true;
|
||||
}
|
||||
|
||||
// If we have CompressedSize, there is data to be read
|
||||
|
||||
@@ -59,6 +59,7 @@ internal partial class StreamingZipHeaderFactory : ZipHeaderFactory
|
||||
crc = reader.ReadUInt32();
|
||||
}
|
||||
_lastEntryHeader.Crc = crc;
|
||||
_lastEntryHeader.IsCrcAvailable = true;
|
||||
|
||||
//attempt 32bit read
|
||||
ulong compSize = reader.ReadUInt32();
|
||||
@@ -121,6 +122,7 @@ internal partial class StreamingZipHeaderFactory : ZipHeaderFactory
|
||||
crc = reader.ReadUInt32();
|
||||
}
|
||||
_lastEntryHeader.Crc = crc;
|
||||
_lastEntryHeader.IsCrcAvailable = true;
|
||||
|
||||
// The DataDescriptor can be either 64bit or 32bit
|
||||
var compressed_size = reader.ReadUInt32();
|
||||
@@ -188,6 +190,7 @@ internal partial class StreamingZipHeaderFactory : ZipHeaderFactory
|
||||
local_header.UncompressedSize = dir_header.Size;
|
||||
local_header.CompressedSize = dir_header.CompressedSize;
|
||||
local_header.Crc = (uint)dir_header.Crc;
|
||||
local_header.IsCrcAvailable = true;
|
||||
}
|
||||
|
||||
// If we have CompressedSize, there is data to be read
|
||||
|
||||
@@ -89,6 +89,49 @@ public class ZipEntry : Entry
|
||||
|
||||
public override long Crc => _filePart?.Header.Crc ?? 0;
|
||||
|
||||
internal override ChecksumDescriptor Checksum
|
||||
{
|
||||
get
|
||||
{
|
||||
if (
|
||||
_filePart is null
|
||||
|| IsDirectory
|
||||
|| !_filePart.Header.IsCrcAvailable
|
||||
|| !IsReliableCrcMetadata(_filePart.Header)
|
||||
)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new ChecksumDescriptor(
|
||||
ChecksumKind.Crc32,
|
||||
_filePart.Header.Crc,
|
||||
IsAvailable: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsReliableCrcMetadata(ZipFileEntry header)
|
||||
{
|
||||
if (header.CompressionMethod != ZipCompressionMethod.WinzipAes)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var aesExtraData = header.Extra.FirstOrDefault(x => x.Type == ExtraDataType.WinZipAes);
|
||||
if (aesExtraData is null || aesExtraData.DataBytes.Length < MinimumWinZipAesExtraDataLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var vendorVersion = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(
|
||||
aesExtraData.DataBytes
|
||||
);
|
||||
|
||||
// WinZip AES AE-2 stores a zero CRC field by design and relies on AES authentication.
|
||||
return vendorVersion == 0x0001;
|
||||
}
|
||||
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
@@ -44,6 +44,7 @@ internal partial class ZipHeaderFactory
|
||||
)
|
||||
{
|
||||
_lastEntryHeader.Crc = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
_lastEntryHeader.IsCrcAvailable = true;
|
||||
_lastEntryHeader.CompressedSize = zip64
|
||||
? (long)await reader.ReadUInt64Async().ConfigureAwait(false)
|
||||
: await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
|
||||
@@ -66,6 +66,7 @@ internal partial class ZipHeaderFactory
|
||||
)
|
||||
{
|
||||
_lastEntryHeader.Crc = reader.ReadUInt32();
|
||||
_lastEntryHeader.IsCrcAvailable = true;
|
||||
_lastEntryHeader.CompressedSize = zip64
|
||||
? (long)reader.ReadUInt64()
|
||||
: reader.ReadUInt32();
|
||||
|
||||
@@ -119,7 +119,7 @@ public sealed class Crc32Stream : Stream
|
||||
public static uint Compute(uint polynomial, uint seed, ReadOnlySpan<byte> buffer) =>
|
||||
~CalculateCrc(InitializeTable(polynomial), seed, buffer);
|
||||
|
||||
private static uint[] InitializeTable(uint polynomial)
|
||||
internal static uint[] InitializeTable(uint polynomial)
|
||||
{
|
||||
if (polynomial == DEFAULT_POLYNOMIAL && _defaultTable != null)
|
||||
{
|
||||
@@ -153,7 +153,7 @@ public sealed class Crc32Stream : Stream
|
||||
return createTable;
|
||||
}
|
||||
|
||||
private static uint CalculateCrc(uint[] table, uint crc, ReadOnlySpan<byte> buffer)
|
||||
internal static uint CalculateCrc(uint[] table, uint crc, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
@@ -165,6 +165,6 @@ public sealed class Crc32Stream : Stream
|
||||
return crc;
|
||||
}
|
||||
|
||||
private static uint CalculateCrc(uint[] table, uint crc, byte b) =>
|
||||
internal static uint CalculateCrc(uint[] table, uint crc, byte b) =>
|
||||
(crc >> 8) ^ table[(crc ^ b) & 0xFF];
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ public static class IAsyncReaderExtensions
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
)
|
||||
{
|
||||
options ??= new ExtractionOptions();
|
||||
await reader
|
||||
.Entry.WriteEntryToFileAsync(
|
||||
destinationFileName,
|
||||
@@ -44,12 +46,12 @@ public static class IAsyncReaderExtensions
|
||||
async (x, fm, ct) =>
|
||||
{
|
||||
using var fs = File.Open(x, fm);
|
||||
await CopyEntryToAsync(reader, fs, options?.BufferSize, ct)
|
||||
.ConfigureAwait(false);
|
||||
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract all remaining unread entries to specific directory asynchronously, retaining filename
|
||||
@@ -72,7 +74,9 @@ public static class IAsyncReaderExtensions
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
)
|
||||
{
|
||||
options ??= new ExtractionOptions();
|
||||
await reader
|
||||
.Entry.WriteEntryToFileAsync(
|
||||
destinationFileName,
|
||||
@@ -80,12 +84,12 @@ public static class IAsyncReaderExtensions
|
||||
async (x, fm, ct) =>
|
||||
{
|
||||
using var fs = File.Open(x, fm);
|
||||
await CopyEntryToAsync(reader, fs, options?.BufferSize, ct)
|
||||
.ConfigureAwait(false);
|
||||
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask WriteEntryToAsync(
|
||||
FileInfo destinationFileInfo,
|
||||
@@ -100,7 +104,7 @@ public static class IAsyncReaderExtensions
|
||||
private static async ValueTask CopyEntryToAsync(
|
||||
IAsyncReader reader,
|
||||
Stream writableStream,
|
||||
int? bufferSize,
|
||||
ExtractionOptions options,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
@@ -113,9 +117,14 @@ public static class IAsyncReaderExtensions
|
||||
.OpenEntryStreamAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
#endif
|
||||
var sourceStream = WrapWithProgress(entryStream, reader.Entry);
|
||||
var checkedStream = IEntryExtensions.WrapWithChecksumValidation(
|
||||
reader.Entry,
|
||||
entryStream,
|
||||
options
|
||||
);
|
||||
var sourceStream = WrapWithProgress(checkedStream, reader.Entry);
|
||||
await sourceStream
|
||||
.CopyToAsync(writableStream, bufferSize ?? Constants.BufferSize, cancellationToken)
|
||||
.CopyToAsync(writableStream, options.BufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,26 +51,35 @@ public static class IReaderExtensions
|
||||
/// <summary>
|
||||
/// Extract to specific file
|
||||
/// </summary>
|
||||
public void WriteEntryToFile(
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null
|
||||
) =>
|
||||
public void WriteEntryToFile(string destinationFileName, ExtractionOptions? options = null)
|
||||
{
|
||||
options ??= new ExtractionOptions();
|
||||
reader.Entry.WriteEntryToFile(
|
||||
destinationFileName,
|
||||
options,
|
||||
(x, fm) =>
|
||||
{
|
||||
using var fs = File.Open(x, fm);
|
||||
CopyEntryTo(reader, fs, options?.BufferSize ?? Constants.BufferSize);
|
||||
CopyEntryTo(reader, fs, options ?? new ExtractionOptions());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyEntryTo(IReader reader, Stream writableStream, int bufferSize)
|
||||
private static void CopyEntryTo(
|
||||
IReader reader,
|
||||
Stream writableStream,
|
||||
ExtractionOptions options
|
||||
)
|
||||
{
|
||||
using var entryStream = reader.OpenEntryStream();
|
||||
var sourceStream = WrapWithProgress(entryStream, reader.Entry);
|
||||
sourceStream.CopyTo(writableStream, bufferSize);
|
||||
var checkedStream = IEntryExtensions.WrapWithChecksumValidation(
|
||||
reader.Entry,
|
||||
entryStream,
|
||||
options
|
||||
);
|
||||
var sourceStream = WrapWithProgress(checkedStream, reader.Entry);
|
||||
sourceStream.CopyTo(writableStream, options.BufferSize);
|
||||
}
|
||||
|
||||
private static Stream WrapWithProgress(Stream source, IEntry entry)
|
||||
|
||||
Reference in New Issue
Block a user