diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 23d5920b..b64b3c86 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -105,14 +105,15 @@ namespace SharpCompress.Archives.GZip { // read the header on the first read using var header = MemoryPool.Shared.Rent(10); + var slice = header.Memory.Slice(0, 10); // workitem 8501: handle edge case (decompress empty stream) - if (!await stream.ReadFullyAsync(header.Memory.Slice(0, 10), cancellationToken)) + if (!await stream.ReadFullyAsync(, cancellationToken)) { return false; } - if (header.Memory.Span[0] != 0x1F || header.Memory.Span[1] != 0x8B || header.Memory.Span[2] != 8) + if (slice.Span[0] != 0x1F || slice.Span[1] != 0x8B || slice.Span[2] != 8) { return false; } diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index b71b2147..0b191056 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -10,7 +10,8 @@ using SharpCompress.Readers.Rar; namespace SharpCompress.Archives.Rar { - public class RarArchive : AbstractArchive + public class + RarArchive : AbstractArchive { internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); @@ -42,7 +43,7 @@ namespace SharpCompress.Archives.Rar protected override IEnumerable LoadEntries(IEnumerable volumes) { - return RarArchiveEntryFactory.GetEntries(this, volumes); + return RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions); } protected override IEnumerable LoadVolumes(IEnumerable streams) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index eee9d996..9f9dee0c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -6,6 +6,7 @@ using SharpCompress.Common; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; using SharpCompress.Compressors.Rar; +using SharpCompress.Readers; namespace SharpCompress.Archives.Rar { @@ -13,11 +14,13 @@ namespace SharpCompress.Archives.Rar { private readonly ICollection parts; private readonly RarArchive archive; + private readonly ReaderOptions readerOptions; - internal RarArchiveEntry(RarArchive archive, IEnumerable parts) + internal RarArchiveEntry(RarArchive archive, IEnumerable parts, ReaderOptions readerOptions) { this.parts = parts.ToList(); this.archive = archive; + this.readerOptions = readerOptions; } public override CompressionType CompressionType => CompressionType.Rar; @@ -69,13 +72,14 @@ namespace SharpCompress.Archives.Rar { get { - return parts.Select(fp => fp.FileHeader).Any(fh => !fh.IsSplitAfter); + var headers = parts.Select(x => x.FileHeader); + return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter; } } private void CheckIncomplete() { - if (!IsComplete) + if (!readerOptions.DisableCheckIncomplete && !IsComplete) { throw new IncompleteArchiveException("ArchiveEntry is incomplete and cannot perform this operation."); } diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs index e41c024d..2d471c18 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using SharpCompress.Common.Rar; +using SharpCompress.Readers; namespace SharpCompress.Archives.Rar { @@ -36,11 +37,12 @@ namespace SharpCompress.Archives.Rar } internal static IEnumerable GetEntries(RarArchive archive, - IEnumerable rarParts) + IEnumerable rarParts, + ReaderOptions readerOptions) { foreach (var groupedParts in GetMatchedFileParts(rarParts)) { - yield return new RarArchiveEntry(archive, groupedParts); + yield return new RarArchiveEntry(archive, groupedParts, readerOptions); } } } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 11d7883f..2c361a09 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -437,6 +437,7 @@ namespace SharpCompress.Common.Rar.Headers internal long DataStartPosition { get; set; } public Stream PackedStream { get; set; } + public bool IsSplitBefore => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE); public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER); public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY); diff --git a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs index dcc0df54..78fee933 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs @@ -50,11 +50,11 @@ namespace SharpCompress.Common.Rar if (sizeToRead > 0) { int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); - byte[] cipherText = new byte[RarRijndael.CRYPTO_BLOCK_SIZE]; + Span cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE]; for (int i = 0; i < alignedSize / 16; i++) { //long ax = System.currentTimeMillis(); - _actualStream.Read(cipherText, 0, RarRijndael.CRYPTO_BLOCK_SIZE); + _actualStream.Read(cipherText); var readBytes = _rijndael.ProcessBlock(cipherText); foreach (var readByte in readBytes) diff --git a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs index 5547a8e5..ae6d13cf 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs @@ -1519,6 +1519,7 @@ namespace SharpCompress.Common.SevenZip } } + byte[] buffer = null; foreach (CExtractFolderInfo efi in extractFolderInfoVector) { int startIndex; @@ -1555,7 +1556,7 @@ namespace SharpCompress.Common.SevenZip Stream s = DecoderStreamHelper.CreateDecoderStream(_stream, folderStartPackPos, packSizes, folderInfo, db.PasswordProvider); - byte[] buffer = new byte[4 << 10]; + buffer ??= new byte[4 << 10]; for (; ; ) { int processed = s.Read(buffer, 0, buffer.Length); diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs index f884cc54..31282db4 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs @@ -69,53 +69,37 @@ namespace SharpCompress.Common.Zip.Headers Process(); } - //From the spec values are only in the extradata if the standard - //value is set to 0xFFFF, but if one of the sizes are present, both are. - //Hence if length == 4 volume only - // if length == 8 offset only - // if length == 12 offset + volume - // if length == 16 sizes only - // if length == 20 sizes + volume - // if length == 24 sizes + offset - // if length == 28 everything. - //It is unclear how many of these are used in the wild. - private void Process() { + if (DataBytes.Length >= 8) + { + UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); + } + + if (DataBytes.Length >= 16) + { + CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); + } + + if (DataBytes.Length >= 24) + { + RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16)); + } + + if (DataBytes.Length >= 28) + { + VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(24)); + } + switch (DataBytes.Length) { - case 4: - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes); - return; case 8: - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - return; - case 12: - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(8)); - return; case 16: - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); - return; - case 20: - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(16)); - return; case 24: - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16)); - return; case 28: - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16)); - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(24)); - return; + break; default: - throw new ArchiveException("Unexpected size of of Zip64 extended information extra field"); + throw new ArchiveException($"Unexpected size of of Zip64 extended information extra field: {DataBytes.Length}"); } } diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs index dc445c04..309e9a33 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -12,7 +12,10 @@ namespace SharpCompress.Common.Zip { internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory { - private const int MAX_ITERATIONS_FOR_DIRECTORY_HEADER = 4096; + private const int MINIMUM_EOCD_LENGTH = 22; + private const int ZIP64_EOCD_LENGTH = 20; + // Comment may be within 64kb + structure 22 bytes + private const int MAX_SEARCH_LENGTH_FOR_EOCD = 65557; private bool _zip64; internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) @@ -22,14 +25,26 @@ namespace SharpCompress.Common.Zip internal async IAsyncEnumerable ReadSeekableHeader(Stream stream, [EnumeratorCancellation]CancellationToken cancellationToken) { - await SeekBackToHeader(stream, DIRECTORY_END_HEADER_BYTES, cancellationToken); + var reader = new BinaryReader(stream); + + SeekBackToHeader(stream, reader); + + var eocd_location = stream.Position; var entry = new DirectoryEndHeader(); await entry.Read(stream, cancellationToken); if (entry.IsZip64) { _zip64 = true; - await SeekBackToHeader(stream, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR, cancellationToken); + + // ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD + stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin); + uint zip64_locator = reader.ReadUInt32(); + if( zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR ) + { + throw new ArchiveException("Failed to locate the Zip64 Directory Locator"); + } + var zip64Locator = new Zip64DirectoryEndLocatorHeader(); await zip64Locator.Read(stream, cancellationToken); @@ -75,27 +90,49 @@ namespace SharpCompress.Common.Zip } } - private static async ValueTask SeekBackToHeader(Stream stream, uint headerSignature, CancellationToken cancellationToken) + private static bool IsMatch( byte[] haystack, int position, byte[] needle) { - long offset = 0; - uint signature; - int iterationCount = 0; - do + for( int i = 0; i < needle.Length; i++ ) { - if ((stream.Length + offset) - 4 < 0) + if( haystack[ position + i ] != needle[ i ] ) { - throw new ArchiveException("Failed to locate the Zip Header"); - } - stream.Seek(offset - 4, SeekOrigin.End); - signature = await stream.ReadLittleEndianUInt32(cancellationToken); - offset--; - iterationCount++; - if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER) - { - throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted."); + return false; } } - while (signature != headerSignature); + + return true; + } + private static void SeekBackToHeader(Stream stream, BinaryReader reader) + { + // Minimum EOCD length + if (stream.Length < MINIMUM_EOCD_LENGTH) + { + throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted."); + } + + int len = stream.Length < MAX_SEARCH_LENGTH_FOR_EOCD ? (int)stream.Length : MAX_SEARCH_LENGTH_FOR_EOCD; + // We search for marker in reverse to find the first occurance + byte[] needle = { 0x06, 0x05, 0x4b, 0x50 }; + + stream.Seek(-len, SeekOrigin.End); + + byte[] seek = reader.ReadBytes(len); + + // Search in reverse + Array.Reverse(seek); + + var max_search_area = len - MINIMUM_EOCD_LENGTH; + + for( int pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end) + { + if( IsMatch(seek, pos_from_end, needle) ) + { + stream.Seek(-pos_from_end, SeekOrigin.End); + return; + } + } + + throw new ArchiveException("Failed to locate the Zip Header"); } internal async ValueTask GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader, CancellationToken cancellationToken) diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index 322cedb5..ea8aea3f 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -75,7 +75,7 @@ namespace SharpCompress.Common.Zip if (disposing) { //read out last 10 auth bytes - var ten = new byte[10]; + Span ten = stackalloc byte[10]; _stream.ReadFully(ten); _stream.Dispose(); } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index c1c73a10..faa98712 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -260,7 +260,7 @@ namespace SharpCompress.Compressors.Deflate if (_z.AvailableBytesIn != 8) { // Make sure we have read to the end of the stream - Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, _z.AvailableBytesIn); + _z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer); int bytesNeeded = 8 - _z.AvailableBytesIn; int bytesRead = await _stream.ReadAsync(trailer, _z.AvailableBytesIn, @@ -272,7 +272,7 @@ namespace SharpCompress.Compressors.Deflate } else { - Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, trailer.Length); + _z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer); } Int32 crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer); diff --git a/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs b/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs index 8b62388b..08eef12c 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs @@ -31,7 +31,11 @@ namespace SharpCompress.Compressors.PPMd public PpmdVersion Version { get; } = PpmdVersion.I1; internal ModelRestorationMethod RestorationMethod { get; } - public PpmdProperties(byte[] properties) + public PpmdProperties(byte[] properties) : this(properties.AsSpan()) + { + } + + public PpmdProperties(ReadOnlySpan properties) { if (properties.Length == 2) { @@ -43,7 +47,7 @@ namespace SharpCompress.Compressors.PPMd else if (properties.Length == 5) { Version = PpmdVersion.H7Z; - AllocatorSize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1)); + AllocatorSize = BinaryPrimitives.ReadInt32LittleEndian(properties.Slice(1)); ModelOrder = properties[0]; } } diff --git a/src/SharpCompress/Compressors/Xz/BinaryUtils.cs b/src/SharpCompress/Compressors/Xz/BinaryUtils.cs index a56c2ae1..67eba65a 100644 --- a/src/SharpCompress/Compressors/Xz/BinaryUtils.cs +++ b/src/SharpCompress/Compressors/Xz/BinaryUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Binary; using System.Buffers; using System.IO; using System.Threading; @@ -11,7 +12,7 @@ namespace SharpCompress.Compressors.Xz public static int ReadLittleEndianInt32(this BinaryReader reader) { byte[] bytes = reader.ReadBytes(4); - return (bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24)); + return BinaryPrimitives.ReadInt32LittleEndian(bytes); } internal static uint ReadLittleEndianUInt32(this BinaryReader reader) @@ -21,12 +22,13 @@ namespace SharpCompress.Compressors.Xz public static async ValueTask ReadLittleEndianInt32(this Stream stream, CancellationToken cancellationToken) { using var buffer = MemoryPool.Shared.Rent(4); - var read = await stream.ReadAsync(buffer.Memory.Slice(0, 4), cancellationToken); + var slice = buffer.Memory.Slice(0, 4); + var read = await stream.ReadAsync(slice, cancellationToken); if (read != 4) { throw new EndOfStreamException(); } - return (buffer.Memory.Span[0] + (buffer.Memory.Span[1] << 8) + (buffer.Memory.Span[2] << 16) + (buffer.Memory.Span[3] << 24)); + return BinaryPrimitives.ReadInt32LittleEndian(slice.Span); } internal static async ValueTask ReadLittleEndianUInt32(this Stream stream, CancellationToken cancellationToken) diff --git a/src/SharpCompress/Compressors/Xz/Crc32.cs b/src/SharpCompress/Compressors/Xz/Crc32.cs index 79ee5e45..06680131 100644 --- a/src/SharpCompress/Compressors/Xz/Crc32.cs +++ b/src/SharpCompress/Compressors/Xz/Crc32.cs @@ -1,7 +1,6 @@ #nullable disable using System; -using System.Collections.Generic; namespace SharpCompress.Compressors.Xz { @@ -24,7 +23,7 @@ namespace SharpCompress.Compressors.Xz public static UInt32 Compute(UInt32 polynomial, UInt32 seed, ReadOnlyMemory buffer) { - return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + return ~CalculateHash(InitializeTable(polynomial), seed, buffer); } private static UInt32[] InitializeTable(UInt32 polynomial) @@ -61,16 +60,16 @@ namespace SharpCompress.Compressors.Xz return createTable; } - private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, ReadOnlyMemory buffer, int start, int size) + private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, ReadOnlySpan buffer) { var crc = seed; - for (var i = start; i < size - start; i++) + int len = buffer.Length; + for (var i = 0; i < len; i++) { - crc = (crc >> 8) ^ table[buffer.Span[i] ^ crc & 0xff]; + crc = (crc >> 8) ^ table[(buffer[i] ^ crc) & 0xff]; } return crc; } - } } diff --git a/src/SharpCompress/Compressors/Xz/Crc64.cs b/src/SharpCompress/Compressors/Xz/Crc64.cs index 340d0895..1cc9e1b3 100644 --- a/src/SharpCompress/Compressors/Xz/Crc64.cs +++ b/src/SharpCompress/Compressors/Xz/Crc64.cs @@ -22,14 +22,14 @@ namespace SharpCompress.Compressors.Xz { Table ??= CreateTable(Iso3309Polynomial); - return CalculateHash(seed, Table, buffer, 0, buffer.Length); + return CalculateHash(seed, Table, buffer); } - public static UInt64 CalculateHash(UInt64 seed, UInt64[] table, IList buffer, int start, int size) + public static UInt64 CalculateHash(UInt64 seed, UInt64[] table, ReadOnlySpan buffer) { var crc = seed; - - for (var i = start; i < size; i++) + int len = buffer.Length; + for (var i = 0; i < len; i++) { unchecked { diff --git a/src/SharpCompress/Polyfills/StreamExtensions.cs b/src/SharpCompress/Polyfills/StreamExtensions.cs index 2df2112d..16299adc 100644 --- a/src/SharpCompress/Polyfills/StreamExtensions.cs +++ b/src/SharpCompress/Polyfills/StreamExtensions.cs @@ -12,7 +12,7 @@ namespace System.IO try { - int read = stream.Read(temp, 0, buffer.Length); + int read = stream.Read(buffer); temp.AsSpan(0, read).CopyTo(buffer); @@ -42,4 +42,4 @@ namespace System.IO } } -#endif \ No newline at end of file +#endif diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index 15302dc2..110b0c33 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -10,5 +10,7 @@ namespace SharpCompress.Readers public bool LookForHeader { get; set; } public string? Password { get; set; } + + public bool DisableCheckIncomplete { get; set; } } } \ No newline at end of file diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index e5908d6a..d7504647 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -2,9 +2,9 @@ SharpCompress - Pure C# Decompression/Compression en-US - 0.27.1 - 0.27.1 - 0.27.1 + 0.28.0 + 0.28.0 + 0.28.0 Adam Hathcock netstandard2.1;netcoreapp3.1;net5.0 true diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 37028284..cdddc113 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -378,11 +378,11 @@ namespace SharpCompress return ArrayPool.Shared.Rent(81920); } - public static bool ReadFully(this Stream stream, byte[] buffer) + public static bool ReadFully(this Stream stream, Span buffer) { int total = 0; int read; - while ((read = stream.Read(buffer, total, buffer.Length - total)) > 0) + while ((read = stream.Read(buffer.Slice(total, buffer.Length - total))) > 0) { total += read; if (total >= buffer.Length) diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index df94b83a..3a7dc416 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -136,4 +136,4 @@ namespace SharpCompress.Writers.Tar } } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index bc5c27d7..ec4cf7b0 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -163,10 +163,10 @@ namespace SharpCompress.Writers.Zip var explicitZipCompressionInfo = ToZipCompressionMethod(zipWriterEntryOptions.CompressionType ?? compressionType); byte[] encodedFilename = WriterOptions.ArchiveEncoding.Encode(filename); - // TODO: Use stackalloc when we exclusively support netstandard2.1 or higher - using var intBuf = MemoryPool.Shared.Rent(4); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, ZipHeaderFactory.ENTRY_HEADER_BYTES); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,4), cancellationToken); + using var buffer = MemoryPool.Shared.Rent(4); + var intBuf = buffer.Memory.Slice(0, 4); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, ZipHeaderFactory.ENTRY_HEADER_BYTES); + await OutputStream.WriteAsync(intBuf, cancellationToken); if (explicitZipCompressionInfo == ZipCompressionMethod.Deflate) { if (OutputStream.CanSeek && useZip64) @@ -202,12 +202,12 @@ namespace SharpCompress.Writers.Zip } } - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)flags); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,2), cancellationToken); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)explicitZipCompressionInfo); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,2), cancellationToken); // zipping method - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, zipWriterEntryOptions.ModificationDateTime.DateTimeToDosTime()); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,4), cancellationToken); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)flags); + await OutputStream.WriteAsync(intBuf.Slice(0,2), cancellationToken); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)explicitZipCompressionInfo); + await OutputStream.WriteAsync(intBuf.Slice(0,2), cancellationToken); // zipping method + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, zipWriterEntryOptions.ModificationDateTime.DateTimeToDosTime()); + await OutputStream.WriteAsync(intBuf, cancellationToken); // zipping date and time using var buf2 = MemoryPool.Shared.Rent(12); @@ -215,8 +215,8 @@ namespace SharpCompress.Writers.Zip await OutputStream.WriteAsync(buf2.Memory.Slice(0, 12), cancellationToken); // unused CRC, un/compressed size, updated later - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)encodedFilename.Length); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,2), cancellationToken);// filename length + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)encodedFilename.Length); + await OutputStream.WriteAsync(intBuf.Slice(0,2), cancellationToken);// filename length var extralength = 0; if (OutputStream.CanSeek && useZip64) @@ -224,8 +224,8 @@ namespace SharpCompress.Writers.Zip extralength = 2 + 2 + 8 + 8; } - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)extralength); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,2), cancellationToken);// extra length + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)extralength); + await OutputStream.WriteAsync(intBuf.Slice(0,2), cancellationToken);// extra length await OutputStream.WriteAsync(encodedFilename.AsMemory(), cancellationToken); if (extralength != 0) @@ -241,13 +241,14 @@ namespace SharpCompress.Writers.Zip private async Task WriteFooterAsync(uint crc, uint compressed, uint uncompressed) { - using var intBuf = MemoryPool.Shared.Rent(4); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, crc); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, compressed); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, uncompressed); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); + using var buffer = MemoryPool.Shared.Rent(4); + var intBuf = buffer.Memory.Slice(0, 4); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, crc); + await OutputStream.WriteAsync(intBuf); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, compressed); + await OutputStream.WriteAsync(intBuf); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, uncompressed); + await OutputStream.WriteAsync(intBuf); } private async Task WriteEndRecordAsync(ulong size) @@ -258,76 +259,76 @@ namespace SharpCompress.Writers.Zip var sizevalue = size >= uint.MaxValue ? uint.MaxValue : (uint)size; var streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streamPosition; - using var intBuf = MemoryPool.Shared.Rent(8); + using var buffer = MemoryPool.Shared.Rent(8); + var intBuf = buffer.Memory.Slice(0, 8); if (zip64) { var recordlen = 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8; // Write zip64 end of central directory record - var s = intBuf.Memory.Slice(0, 4); + var s = intBuf.Slice(0, 4); s.Span[0] = 80; s.Span[1] = 75; s.Span[2] = 6; s.Span[3] = 6; await OutputStream.WriteAsync(s); - BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Memory.Span, (ulong)recordlen); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8));// Size of zip64 end of central directory record - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, 0); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 2)); // Made by - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, 45); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 2)); // Version needed + BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Span, (ulong)recordlen); + await OutputStream.WriteAsync(intBuf.Slice(0,8));// Size of zip64 end of central directory record + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, 45); + await OutputStream.WriteAsync(intBuf.Slice(0, 2)); // Made by + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, 45); + await OutputStream.WriteAsync(intBuf.Slice(0, 2)); // Version needed - intBuf.Memory.Span.Clear(); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); // Disk number - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); // Central dir disk + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, 0); + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); // Disk number + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); // Central dir disk // TODO: entries.Count is int, so max 2^31 files - BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Memory.Span, (ulong)entries.Count); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); // Entries in this disk - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); // Total entries - BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Memory.Span, size); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); // Central Directory size - BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Memory.Span, (ulong)streamPosition); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); // Disk offset + BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Span, (ulong)entries.Count); + await OutputStream.WriteAsync(intBuf); // Entries in this disk + await OutputStream.WriteAsync(intBuf); // Total entries + BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Span, size); + await OutputStream.WriteAsync(intBuf); // Central Directory size + BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Span, (ulong)streamPosition); + await OutputStream.WriteAsync(intBuf); // Disk offset // Write zip64 end of central directory locator - s = intBuf.Memory.Slice(0, 4); + s = intBuf.Slice(0, 4); s.Span[0] = 80; s.Span[1] = 75; s.Span[2] = 6; s.Span[3] = 7; await OutputStream.WriteAsync(s); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, 0); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); // Entry disk - BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Memory.Span, (ulong)streamPosition + size); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); // Offset to the zip64 central directory - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, 0); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); // Number of disks + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, 0); + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); // Entry disk + BinaryPrimitives.WriteUInt64LittleEndian(intBuf.Span, (ulong)streamPosition + size); + await OutputStream.WriteAsync(intBuf); // Offset to the zip64 central directory + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, 1); + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); // Number of disks streamPosition += recordlen + (4 + 4 + 8 + 4); streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streampositionvalue; } // Write normal end of central directory record - intBuf.Memory.Span.Clear(); - var x = intBuf.Memory.Slice(0, 4); - x.Span[0] = 80; - x.Span[1] = 75; - x.Span[2] = 5; - x.Span[3] = 6; - await OutputStream.WriteAsync(intBuf.Memory.Slice(0,8)); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)entries.Count); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 2)); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 2));//TODO: this is twice? - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, sizevalue); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, streampositionvalue); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 4)); + intBuf.Span.Clear(); + intBuf.Span[0] = 80; + intBuf.Span[1] = 75; + intBuf.Span[2] = 5; + intBuf.Span[3] = 6; + await OutputStream.WriteAsync(intBuf); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)entries.Count); + await OutputStream.WriteAsync(intBuf.Slice(0, 2)); + await OutputStream.WriteAsync(intBuf.Slice(0, 2));//TODO: this is twice? + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, sizevalue); + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, streampositionvalue); + await OutputStream.WriteAsync(intBuf.Slice(0, 4)); byte[] encodedComment = WriterOptions.ArchiveEncoding.Encode(zipComment); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Memory.Span, (ushort)encodedComment.Length); - await OutputStream.WriteAsync(intBuf.Memory.Slice(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf.Span, (ushort)encodedComment.Length); + await OutputStream.WriteAsync(intBuf.Slice(0, 2)); await OutputStream.WriteAsync(encodedComment.AsMemory()); } @@ -514,9 +515,10 @@ namespace SharpCompress.Writers.Zip throw new NotSupportedException("Streams larger than 4GiB are not supported for non-seekable streams"); } - using var intBuf = MemoryPool.Shared.Rent(4); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Memory.Span, ZipHeaderFactory.POST_DATA_DESCRIPTOR); - await originalStream.WriteAsync(intBuf.Memory.Slice(0,4)); + using var buffer = MemoryPool.Shared.Rent(4); + var intBuf = buffer.Memory.Slice(0, 4); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf.Span, ZipHeaderFactory.POST_DATA_DESCRIPTOR); + await originalStream.WriteAsync(intBuf); await writer.WriteFooterAsync(entry.Crc, compressedvalue, decompressedvalue); diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index 3913c0df..aa944a20 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -18,8 +18,8 @@ namespace SharpCompress.Test.GZip { await ReadAsync("Tar.tar.gz", CompressionType.GZip); } - - + + [Fact] public async ValueTask GZip_Reader_Generic2() { diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 02ee2a9c..d10349a7 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -558,5 +558,17 @@ namespace SharpCompress.Test.Zip } } } + + [Fact] + public void Zip_LongComment_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip"); + + using(ZipArchive za = ZipArchive.Open(zipPath)) + { + var count = za.Entries.Count; + Assert.Equal(1, count); + } + } } } diff --git a/tests/TestArchives/Archives/Zip.LongComment.zip b/tests/TestArchives/Archives/Zip.LongComment.zip new file mode 100644 index 00000000..3af586d6 Binary files /dev/null and b/tests/TestArchives/Archives/Zip.LongComment.zip differ