From c06f4bc5a84a8a2c1b040a38c4d07deedf95d162 Mon Sep 17 00:00:00 2001 From: Craig Greenhill Date: Thu, 11 Feb 2021 09:37:59 +0000 Subject: [PATCH 01/10] Zip64 Header and Size fix --- .../Headers/LocalEntryHeaderExtraFactory.cs | 57 ++++--------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs index f884cc54..2e20e80f 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs @@ -69,54 +69,19 @@ 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() { - 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; - default: - throw new ArchiveException("Unexpected size of of Zip64 extended information extra field"); - } + 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)); + + if (DataBytes.Length > 28) + throw new ArchiveException("Unexpected size of of Zip64 extended information extra field"); } public long UncompressedSize { get; private set; } From cd677440ce83d4c982eb786a9d0e452195c76c0d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 12 Feb 2021 20:18:50 +0100 Subject: [PATCH 02/10] Use stackallocs where possible/sensible --- .../Archives/GZip/GZipArchive.cs | 2 +- src/SharpCompress/Common/GZip/GZipFilePart.cs | 4 +- .../Common/Rar/RarCryptoWrapper.cs | 4 +- .../Common/SevenZip/ArchiveReader.cs | 3 +- .../Common/Tar/Headers/TarHeader.cs | 2 +- .../Common/Zip/WinzipAesCryptoStream.cs | 2 +- src/SharpCompress/Common/Zip/ZipFilePart.cs | 2 +- .../Compressors/Deflate/ZlibBaseStream.cs | 12 ++- .../Compressors/LZMA/LZipStream.cs | 8 +- .../Compressors/PPMd/PpmdProperties.cs | 8 +- .../Compressors/Xz/BinaryUtils.cs | 7 +- src/SharpCompress/Compressors/Xz/Crc32.cs | 11 ++- src/SharpCompress/Compressors/Xz/Crc64.cs | 8 +- .../Polyfills/StreamExtensions.cs | 4 +- src/SharpCompress/Utility.cs | 4 +- src/SharpCompress/Writers/Tar/TarWriter.cs | 4 +- .../Writers/Zip/ZipCentralDirectoryEntry.cs | 44 +++++------ src/SharpCompress/Writers/Zip/ZipWriter.cs | 73 +++++++++---------- 18 files changed, 102 insertions(+), 100 deletions(-) diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 025aacaa..8360aad9 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -98,7 +98,7 @@ namespace SharpCompress.Archives.GZip public static bool IsGZipFile(Stream stream) { // read the header on the first read - byte[] header = new byte[10]; + Span header = stackalloc byte[10]; // workitem 8501: handle edge case (decompress empty stream) if (!stream.ReadFully(header)) diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index 03b5b9ae..6edacfe9 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -110,13 +110,13 @@ namespace SharpCompress.Common.GZip private string ReadZeroTerminatedString(Stream stream) { - byte[] buf1 = new byte[1]; + Span buf1 = stackalloc byte[1]; var list = new List(); bool done = false; do { // workitem 7740 - int n = stream.Read(buf1, 0, 1); + int n = stream.Read(buf1); if (n != 1) { throw new ZlibException("Unexpected EOF reading GZIP header."); 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 1ef60c0c..93e0ff69 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs @@ -1517,6 +1517,7 @@ namespace SharpCompress.Common.SevenZip } } + byte[] buffer = null; foreach (CExtractFolderInfo efi in extractFolderInfoVector) { int startIndex; @@ -1553,7 +1554,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/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 9d7c052f..0a3824c0 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -97,7 +97,7 @@ namespace SharpCompress.Common.Tar.Headers { numPaddingBytes = BLOCK_SIZE; } - output.Write(new byte[numPaddingBytes], 0, numPaddingBytes); + output.Write(stackalloc byte[numPaddingBytes]); } internal bool Read(BinaryReader reader) 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/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 23fbaf24..47dd0d60 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -93,7 +93,7 @@ namespace SharpCompress.Common.Zip } case ZipCompressionMethod.PPMd: { - var props = new byte[2]; + Span props = stackalloc byte[2]; stream.ReadFully(props); return new PpmdStream(new PpmdProperties(props), stream, false); } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index 564d15db..439bd225 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -256,17 +256,15 @@ namespace SharpCompress.Compressors.Deflate } // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 - byte[] trailer = new byte[8]; + Span trailer = stackalloc byte[8]; // workitem 8679 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 = _stream.Read(trailer, - _z.AvailableBytesIn, - bytesNeeded); + int bytesRead = _stream.Read(trailer.Slice(_z.AvailableBytesIn, bytesNeeded)); if (bytesNeeded != bytesRead) { throw new ZlibException(String.Format( @@ -276,12 +274,12 @@ 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); Int32 crc32_actual = crc.Crc32Result; - Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.AsSpan(4)); + Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4)); Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF); if (crc32_actual != crc32_expected) diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs index be7c7d38..8e316b06 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -59,16 +59,16 @@ namespace SharpCompress.Compressors.LZMA crc32Stream.Dispose(); var compressedCount = _countingWritableSubStream!.Count; - byte[] intBuf = new byte[8]; + Span intBuf = stackalloc byte[8]; BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc); - _countingWritableSubStream.Write(intBuf, 0, 4); + _countingWritableSubStream.Write(intBuf.Slice(0, 4)); BinaryPrimitives.WriteInt64LittleEndian(intBuf, _writeCount); - _countingWritableSubStream.Write(intBuf, 0, 8); + _countingWritableSubStream.Write(intBuf); //total with headers BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20); - _countingWritableSubStream.Write(intBuf, 0, 8); + _countingWritableSubStream.Write(intBuf); } _finished = true; } 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 73ae5203..12f34bf7 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.IO; namespace SharpCompress.Compressors.Xz @@ -8,7 +9,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) @@ -17,13 +18,13 @@ namespace SharpCompress.Compressors.Xz } public static int ReadLittleEndianInt32(this Stream stream) { - byte[] bytes = new byte[4]; + Span bytes = stackalloc byte[4]; var read = stream.ReadFully(bytes); if (!read) { throw new EndOfStreamException(); } - return (bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24)); + return BinaryPrimitives.ReadInt32LittleEndian(bytes); } internal static uint ReadLittleEndianUInt32(this Stream stream) diff --git a/src/SharpCompress/Compressors/Xz/Crc32.cs b/src/SharpCompress/Compressors/Xz/Crc32.cs index 710cd497..01a90791 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, byte[] 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, IList 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[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/Utility.cs b/src/SharpCompress/Utility.cs index e3de66e6..ffebb152 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -281,11 +281,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 ae7105a6..7f0f05e0 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -99,7 +99,7 @@ namespace SharpCompress.Writers.Tar return; } zeros = 512 - zeros; - OutputStream.Write(new byte[zeros], 0, zeros); + OutputStream.Write(stackalloc byte[zeros]); } protected override void Dispose(bool isDisposing) @@ -128,4 +128,4 @@ namespace SharpCompress.Writers.Tar base.Dispose(isDisposing); } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs b/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs index d936c352..8fe87d1e 100644 --- a/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs +++ b/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs @@ -71,60 +71,60 @@ namespace SharpCompress.Writers.Zip usedCompression = ZipCompressionMethod.None; } - byte[] intBuf = new byte[] { 80, 75, 1, 2, version, 0, version, 0 }; + Span intBuf = stackalloc byte[] { 80, 75, 1, 2, version, 0, version, 0 }; //constant sig, then version made by, then version to extract - outputStream.Write(intBuf, 0, 8); + outputStream.Write(intBuf); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - outputStream.Write(intBuf, 0, 2); + outputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)usedCompression); - outputStream.Write(intBuf, 0, 2); // zipping method + outputStream.Write(intBuf.Slice(0, 2)); // zipping method BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ModificationTime.DateTimeToDosTime()); - outputStream.Write(intBuf, 0, 4); + outputStream.Write(intBuf.Slice(0, 4)); // zipping date and time BinaryPrimitives.WriteUInt32LittleEndian(intBuf, Crc); - outputStream.Write(intBuf, 0, 4); // file CRC + outputStream.Write(intBuf.Slice(0, 4)); // file CRC BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressedvalue); - outputStream.Write(intBuf, 0, 4); // compressed file size + outputStream.Write(intBuf.Slice(0, 4)); // compressed file size BinaryPrimitives.WriteUInt32LittleEndian(intBuf, decompressedvalue); - outputStream.Write(intBuf, 0, 4); // uncompressed file size + outputStream.Write(intBuf.Slice(0, 4)); // uncompressed file size BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); - outputStream.Write(intBuf, 0, 2); // Filename in zip + outputStream.Write(intBuf.Slice(0, 2)); // Filename in zip BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); - outputStream.Write(intBuf, 0, 2); // extra length + outputStream.Write(intBuf.Slice(0, 2)); // extra length BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); - outputStream.Write(intBuf, 0, 2); + outputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); - outputStream.Write(intBuf, 0, 2); // disk=0 + outputStream.Write(intBuf.Slice(0, 2)); // disk=0 BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - outputStream.Write(intBuf, 0, 2); // file type: binary + outputStream.Write(intBuf.Slice(0, 2)); // file type: binary BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - outputStream.Write(intBuf, 0, 2); // Internal file attributes + outputStream.Write(intBuf.Slice(0, 2)); // Internal file attributes BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x8100); - outputStream.Write(intBuf, 0, 2); + outputStream.Write(intBuf.Slice(0, 2)); // External file attributes (normal/readable) BinaryPrimitives.WriteUInt32LittleEndian(intBuf, headeroffsetvalue); - outputStream.Write(intBuf, 0, 4); // Offset of header + outputStream.Write(intBuf.Slice(0, 4)); // Offset of header outputStream.Write(encodedFilename, 0, encodedFilename.Length); if (zip64) { BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); - outputStream.Write(intBuf, 0, 2); + outputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)(extralength - 4)); - outputStream.Write(intBuf, 0, 2); + outputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Decompressed); - outputStream.Write(intBuf, 0, 8); + outputStream.Write(intBuf); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Compressed); - outputStream.Write(intBuf, 0, 8); + outputStream.Write(intBuf); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, HeaderOffset); - outputStream.Write(intBuf, 0, 8); + outputStream.Write(intBuf); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - outputStream.Write(intBuf, 0, 4); // VolumeNumber = 0 + outputStream.Write(intBuf.Slice(0, 4)); // VolumeNumber = 0 } outputStream.Write(encodedComment, 0, encodedComment.Length); diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 74ae7145..8bf0faba 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -162,10 +162,9 @@ 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 - byte[] intBuf = new byte[4]; + Span intBuf = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ZipHeaderFactory.ENTRY_HEADER_BYTES); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf); if (explicitZipCompressionInfo == ZipCompressionMethod.Deflate) { if (OutputStream.CanSeek && useZip64) @@ -193,18 +192,18 @@ namespace SharpCompress.Writers.Zip } BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - OutputStream.Write(intBuf, 0, 2); + OutputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)explicitZipCompressionInfo); - OutputStream.Write(intBuf, 0, 2); // zipping method + OutputStream.Write(intBuf.Slice(0, 2)); // zipping method BinaryPrimitives.WriteUInt32LittleEndian(intBuf, zipWriterEntryOptions.ModificationDateTime.DateTimeToDosTime()); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf); // zipping date and time OutputStream.Write(stackalloc byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); // unused CRC, un/compressed size, updated later BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); - OutputStream.Write(intBuf, 0, 2); // filename length + OutputStream.Write(intBuf.Slice(0, 2)); // filename length var extralength = 0; if (OutputStream.CanSeek && useZip64) @@ -213,7 +212,7 @@ namespace SharpCompress.Writers.Zip } BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); - OutputStream.Write(intBuf, 0, 2); // extra length + OutputStream.Write(intBuf.Slice(0, 2)); // extra length OutputStream.Write(encodedFilename, 0, encodedFilename.Length); if (extralength != 0) @@ -227,13 +226,13 @@ namespace SharpCompress.Writers.Zip private void WriteFooter(uint crc, uint compressed, uint uncompressed) { - byte[] intBuf = new byte[4]; + Span intBuf = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressed); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, uncompressed); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf); } private void WriteEndRecord(ulong size) @@ -244,7 +243,7 @@ namespace SharpCompress.Writers.Zip var sizevalue = size >= uint.MaxValue ? uint.MaxValue : (uint)size; var streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streamPosition; - byte[] intBuf = new byte[8]; + Span intBuf = stackalloc byte[8]; if (zip64) { var recordlen = 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8; @@ -253,34 +252,34 @@ namespace SharpCompress.Writers.Zip OutputStream.Write(stackalloc byte[] { 80, 75, 6, 6 }); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)recordlen); - OutputStream.Write(intBuf, 0, 8); // Size of zip64 end of central directory record + OutputStream.Write(intBuf); // Size of zip64 end of central directory record BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); - OutputStream.Write(intBuf, 0, 2); // Made by + OutputStream.Write(intBuf.Slice(0, 2)); // Made by BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); - OutputStream.Write(intBuf, 0, 2); // Version needed + OutputStream.Write(intBuf.Slice(0, 2)); // Version needed BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf, 0, 4); // Disk number - OutputStream.Write(intBuf, 0, 4); // Central dir disk + OutputStream.Write(intBuf.Slice(0, 4)); // Disk number + OutputStream.Write(intBuf.Slice(0, 4)); // Central dir disk // TODO: entries.Count is int, so max 2^31 files BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)entries.Count); - OutputStream.Write(intBuf, 0, 8); // Entries in this disk - OutputStream.Write(intBuf, 0, 8); // Total entries + OutputStream.Write(intBuf); // Entries in this disk + OutputStream.Write(intBuf); // Total entries BinaryPrimitives.WriteUInt64LittleEndian(intBuf, size); - OutputStream.Write(intBuf, 0, 8); // Central Directory size + OutputStream.Write(intBuf); // Central Directory size BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition); - OutputStream.Write(intBuf, 0, 8); // Disk offset + OutputStream.Write(intBuf); // Disk offset // Write zip64 end of central directory locator OutputStream.Write(stackalloc byte[] { 80, 75, 6, 7 }); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf, 0, 4); // Entry disk + OutputStream.Write(intBuf.Slice(0, 4)); // Entry disk BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition + size); - OutputStream.Write(intBuf, 0, 8); // Offset to the zip64 central directory + OutputStream.Write(intBuf); // Offset to the zip64 central directory BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf, 0, 4); // Number of disks + OutputStream.Write(intBuf); // Number of disks streamPosition += recordlen + (4 + 4 + 8 + 4); streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streampositionvalue; @@ -289,15 +288,15 @@ namespace SharpCompress.Writers.Zip // Write normal end of central directory record OutputStream.Write(stackalloc byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)entries.Count); - OutputStream.Write(intBuf, 0, 2); - OutputStream.Write(intBuf, 0, 2); + OutputStream.Write(intBuf.Slice(0, 2)); + OutputStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, sizevalue); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf.Slice(0, 4)); BinaryPrimitives.WriteUInt32LittleEndian(intBuf, streampositionvalue); - OutputStream.Write(intBuf, 0, 4); + OutputStream.Write(intBuf.Slice(0, 4)); byte[] encodedComment = WriterOptions.ArchiveEncoding.Encode(zipComment); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); - OutputStream.Write(intBuf, 0, 2); + OutputStream.Write(intBuf.Slice(0, 2)); OutputStream.Write(encodedComment, 0, encodedComment.Length); } @@ -443,16 +442,16 @@ namespace SharpCompress.Writers.Zip if (entry.Zip64HeaderOffset != 0) { originalStream.Position = (long)(entry.HeaderOffset + entry.Zip64HeaderOffset); - byte[] intBuf = new byte[8]; + Span intBuf = stackalloc byte[8]; BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); - originalStream.Write(intBuf, 0, 2); + originalStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 8 + 8); - originalStream.Write(intBuf, 0, 2); + originalStream.Write(intBuf.Slice(0, 2)); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Decompressed); - originalStream.Write(intBuf, 0, 8); + originalStream.Write(intBuf); BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Compressed); - originalStream.Write(intBuf, 0, 8); + originalStream.Write(intBuf); } originalStream.Position = writer.streamPosition + (long)entry.Compressed; @@ -471,9 +470,9 @@ namespace SharpCompress.Writers.Zip throw new NotSupportedException("Streams larger than 4GiB are not supported for non-seekable streams"); } - byte[] intBuf = new byte[4]; + Span intBuf = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ZipHeaderFactory.POST_DATA_DESCRIPTOR); - originalStream.Write(intBuf, 0, 4); + originalStream.Write(intBuf); writer.WriteFooter(entry.Crc, compressedvalue, decompressedvalue); From 2dd17e3882bf7d411f8f423825edeb6c45e4a4b0 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 13 Feb 2021 07:05:53 +0000 Subject: [PATCH 03/10] Be explicit about zip64 extra field sizes. Formatting --- .../Headers/LocalEntryHeaderExtraFactory.cs | 35 ++++++++++++++----- .../GZip/GZipReaderTests.cs | 4 +-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs index 2e20e80f..31282db4 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs @@ -72,16 +72,35 @@ namespace SharpCompress.Common.Zip.Headers 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)); + } - if (DataBytes.Length > 28) - throw new ArchiveException("Unexpected size of of Zip64 extended information extra field"); + 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 8: + case 16: + case 24: + case 28: + break; + default: + throw new ArchiveException($"Unexpected size of of Zip64 extended information extra field: {DataBytes.Length}"); + } } public long UncompressedSize { get; private set; } diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index bab72e97..ed308c98 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -17,8 +17,8 @@ namespace SharpCompress.Test.GZip { Read("Tar.tar.gz", CompressionType.GZip); } - - + + [Fact] public void GZip_Reader_Generic2() { From 53393e744e3ed79df30e724774092f86f3b9df0f Mon Sep 17 00:00:00 2001 From: Brendan Grant Date: Sat, 13 Feb 2021 13:33:43 -0600 Subject: [PATCH 04/10] Supporting reading contents of incomplete files --- src/SharpCompress/Archives/Rar/RarArchive.cs | 5 +++-- src/SharpCompress/Archives/Rar/RarArchiveEntry.cs | 7 +++++-- src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs | 6 ++++-- src/SharpCompress/Readers/ReaderOptions.cs | 2 ++ 4 files changed, 14 insertions(+), 6 deletions(-) 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..c695a8b9 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; @@ -75,7 +78,7 @@ namespace SharpCompress.Archives.Rar 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/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 From 5b86c40d5b8ff493e701b9b8170b934b6451cc56 Mon Sep 17 00:00:00 2001 From: Brendan Grant Date: Sat, 13 Feb 2021 13:34:57 -0600 Subject: [PATCH 05/10] Properly detect if RAR is complete at the end or not --- src/SharpCompress/Archives/Rar/RarArchiveEntry.cs | 2 +- src/SharpCompress/Common/Rar/Headers/FileHeader.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index c695a8b9..dbdbd806 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -72,7 +72,7 @@ namespace SharpCompress.Archives.Rar { get { - return parts.Select(fp => fp.FileHeader).Any(fh => !fh.IsSplitAfter); + return parts.Select(fp => fp.FileHeader).Any(fh => !fh.IsSplitBefore && !fh.IsSplitAfter); } } 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); From d1d2758ee07ad5a8f72d3a60a1f5f3e75076daab Mon Sep 17 00:00:00 2001 From: Lars Vahlenberg Date: Sat, 13 Feb 2021 23:57:03 +0100 Subject: [PATCH 06/10] Propsal for handling Zip with long comment --- .../Common/Zip/SeekableZipHeaderFactory.cs | 73 +++++++++++++----- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 12 +++ .../TestArchives/Archives/Zip.LongComment.zip | Bin 0 -> 4261 bytes 3 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 tests/TestArchives/Archives/Zip.LongComment.zip diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs index 59ed3060..86eb7d08 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -8,7 +8,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) @@ -20,14 +23,24 @@ namespace SharpCompress.Common.Zip { var reader = new BinaryReader(stream); - SeekBackToHeader(stream, reader, DIRECTORY_END_HEADER_BYTES); + SeekBackToHeader(stream, reader); + + var eocd_location = stream.Position; var entry = new DirectoryEndHeader(); entry.Read(reader); if (entry.IsZip64) { _zip64 = true; - SeekBackToHeader(stream, reader, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR); + + // 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(); zip64Locator.Read(reader); @@ -73,27 +86,49 @@ namespace SharpCompress.Common.Zip } } - private static void SeekBackToHeader(Stream stream, BinaryReader reader, uint headerSignature) + 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 = reader.ReadUInt32(); - 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 LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader) diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 4ecfb07b..d1dd1d47 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -564,5 +564,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 0000000000000000000000000000000000000000..3af586d6620b11b8c2e58109f9b30e5f4561425f GIT binary patch literal 4261 zcmWIWW@h1H00G?{y@4PahWQy77#tNsQY%Un+%j`g^-3yA0=yZS>=|$=SAptOfYNB1 zxfmc4j0_SC-r=sV%r0JIM5hD1S=m6k85tNE5*Zj6bQu^J7zKRti&AqHG7E}Ja}`qZ zbMlK6iZe?T5_3~abQF^F^NLfGOHxZpixd)5G7B<`lQZ+u6;g9DOY{_c^NW%)6;jhv zOB8Z4lTwTF6><}cixc$}+)9g+Qx!^cN{TX*Q;QYyGK(`I=IbaFmSz?!q?TqD=jNv< zs6Nq&)@f^TAKa%x^;u|jS>SRC0W zWgzz}6sM*rlqD8rmKN(N1eRtN7nY_%J(HSTQd+E#mzu1QUzD5)wl+~AH7zYOIkTj+ z2^4enpcvk zr{G$WnV74PnOBlplv0|jkd&E}ng?R!CW7>W++JD)G8Pm(;26ikeZW|npctv_Gnsa zQEqBpNolT*LJq{cWtk<3si4qGEiTDSRe%Jn4kYpti;_!`A}TL4S4SZ;MIkq_A~UzN z7!tQ&yCDIXm{*bt@&z;oz;O-^NoZW;Bqo;@gW@c`G*zJ(6hEMVP07pyc_FbxA+;#K zSWm$-Pa&}c64RiBqNfm?ngX?|1e`{66w-<^^U^bOauPur^HM=E2(nBE8m|f&sd*_y zsYRJ33VEeDIfm!u0?aXGr8xzqC5a`epm7~WOz0Kg{Yo_V`^S% zA~@I}*+3zu6qJ;86bdpDi&9I95@8t?5=w~R$;nJF1%;0bD0@O<37qmkQ2Ru zq)2!cMid5+1OrK^I`HBHRxE&$3nigiP8WI# zeu=rD7%WaLEKMv?NGZ)!NJ}kE&jcm#oXiqv5djl}BsxeufYj=sW(-haR9sq|oLP`r zf>b`kf&f-xL;VHzEI8W0B{Imn(43lw$l>4!(orbOEK4maN(2{osYQt;h~f#7RzVp( zu@sb3;fbiAD77RLRJw&_mL-A;0B9FIG_!$Sm64dNkdc^_S&|4Y z5_1wuQWK#;QCy;@;F1sWX-Q^Ya%M_tUWr0dPGVjPD6qg46DZj#l;!7?mK1;-3MuEH zQCU!0R9cK$V}X@tf+~oV%v4YY0+pk%LOU^80pi`z5(Rj^faY2q1$aT8S^+L2bQB6o zbILOF5{tm)9L%ARQW8{lD_=2U74w1xzw zW^k$oWm!nO7@Aqof(ViX;7t;cdqANJO$2%hp(P4wnR(!<3sg(N+M_y1DHoD#5=%fe zV{Soy5i~g=f)VTwP|!fjN9Ur%Vpx`eH06=fCQ|tdE*lg;1{dTcCWGPu+8{+34Nb_9 z>R%zX6e-Z*H4!*EKyuL92NW)P3P|Y|DUB*Xt1D=zqs9lcu7_rGXj2I4g4FcX;*vyY z`w$exur?yNegsu-C8_DDMNn@;+Xjh{UXcPc5}-z9=79@LaKjIlXFw*VWTxk378hsc zf*Zbi3J`z65(7*nG$W-}K+8W+%O?k3SAy$&qyz-+SQSAVzR2MK$qJx04LG+$(61=Gh@+G`O2G19eHZUl4!`oAcngh8i0L2Sf88mcY^@R?&mPKl?gM$=S zk)fq$P>BMqN1(w3>KY)5Sp*B@c1V?xnV4J(DpbLNqo)9CxxlJmurR1jhQ>HJZGZv} zQ71rC4I;CF`~nLsc)KP%v9Z;18OEsYW1Eh@(_MHx>c>?xVVy*%tKZBz^5dbja0mc9T literal 0 HcmV?d00001 From 566c49ce53dea080068df95f427b3df2d24d4880 Mon Sep 17 00:00:00 2001 From: Lars Vahlenberg Date: Sun, 14 Feb 2021 02:42:32 +0100 Subject: [PATCH 07/10] Proposal Zip64 requires version 4.5 Number of disks is 4 bytes and not 8 --- src/SharpCompress/Writers/Zip/ZipWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 8bf0faba..01047315 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -253,7 +253,7 @@ namespace SharpCompress.Writers.Zip BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)recordlen); OutputStream.Write(intBuf); // Size of zip64 end of central directory record - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); OutputStream.Write(intBuf.Slice(0, 2)); // Made by BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); OutputStream.Write(intBuf.Slice(0, 2)); // Version needed @@ -278,8 +278,8 @@ namespace SharpCompress.Writers.Zip OutputStream.Write(intBuf.Slice(0, 4)); // Entry disk BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition + size); OutputStream.Write(intBuf); // Offset to the zip64 central directory - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf); // Number of disks + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 1); + OutputStream.Write(intBuf.Slice(0, 4)); // Number of disks streamPosition += recordlen + (4 + 4 + 8 + 4); streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streampositionvalue; From 045093f4537a3865fc434aee299cbefc3eff50a0 Mon Sep 17 00:00:00 2001 From: Lars Vahlenberg Date: Sun, 14 Feb 2021 10:26:26 +0100 Subject: [PATCH 08/10] Linux is case sensitive with files names --- tests/SharpCompress.Test/Zip/ZipArchiveTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index d1dd1d47..d272aa9a 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -568,7 +568,7 @@ namespace SharpCompress.Test.Zip [Fact] public void Zip_LongComment_Read() { - string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.Longcomment.zip"); + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip"); using(ZipArchive za = ZipArchive.Open(zipPath)) { From a51b56339a9c221a2bab2e21877c1295a5551c27 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 14 Feb 2021 13:00:43 +0000 Subject: [PATCH 09/10] Fix complete entry check for RAR files. --- src/SharpCompress/Archives/Rar/RarArchiveEntry.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index dbdbd806..9f9dee0c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -72,7 +72,8 @@ namespace SharpCompress.Archives.Rar { get { - return parts.Select(fp => fp.FileHeader).Any(fh => !fh.IsSplitBefore && !fh.IsSplitAfter); + var headers = parts.Select(x => x.FileHeader); + return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter; } } From 403baf05a671eb30580453aa6e6a22875096dddc Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 14 Feb 2021 13:07:35 +0000 Subject: [PATCH 10/10] Mark for 0.28 --- src/SharpCompress/SharpCompress.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 782f3651..7dbd195f 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.0;netstandard2.1;netcoreapp3.1;net5.0 true