From 2b0a3551a615dd3274a54323d0b903885ef26313 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Mon, 16 Oct 2023 18:13:32 +0200 Subject: [PATCH] Improved signature lookup Caching the part of the stream which potentially contains the signature improves the performance in case of streams where seeking backwards is costly --- src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs | 39 +++++++++++-------- .../Zip/ZipEncryptionHandling.cs | 8 ++-- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs index ec63d7943..4ecc86189 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs @@ -1,8 +1,8 @@ +using ICSharpCode.SharpZipLib.Core; using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using ICSharpCode.SharpZipLib.Core; namespace ICSharpCode.SharpZipLib.Zip { @@ -47,7 +47,7 @@ internal static class ZipFormat { // Write the local file header // TODO: ZipFormat.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage - internal static int WriteLocalHeader(Stream stream, ZipEntry entry, out EntryPatchData patchData, + internal static int WriteLocalHeader(Stream stream, ZipEntry entry, out EntryPatchData patchData, bool headerInfoAvailable, bool patchEntryHeader, long streamOffset, StringCodec stringCodec) { patchData = new EntryPatchData(); @@ -76,7 +76,7 @@ internal static int WriteLocalHeader(Stream stream, ZipEntry entry, out EntryPat { if (patchEntryHeader) patchData.CrcPatchOffset = streamOffset + stream.Position; - + stream.WriteLEInt(0); // Crc if (patchEntryHeader) @@ -178,21 +178,28 @@ internal static long LocateBlockWithSignature(Stream stream, int signature, long long giveUpMarker = Math.Max(pos - maximumVariableData, 0); - // TODO: This loop could be optimized for speed. + // cache part of stream which should be searched for signature + byte[] cache = new byte[(int)(pos - giveUpMarker) + 4]; + stream.Seek(giveUpMarker, SeekOrigin.Begin); + stream.Read(cache, 0, cache.Length); + MemoryStream cacheStream = new MemoryStream(cache); + do { if (pos < giveUpMarker) { return -1; } - stream.Seek(pos--, SeekOrigin.Begin); - } while (stream.ReadLEInt() != signature); + cacheStream.Seek(pos - giveUpMarker, SeekOrigin.Begin); + pos--; + } while (cacheStream.ReadLEInt() != signature); + stream.Seek(pos + 5, SeekOrigin.Begin); return stream.Position; } /// - public static async Task WriteZip64EndOfCentralDirectoryAsync(Stream stream, long noOfEntries, + public static async Task WriteZip64EndOfCentralDirectoryAsync(Stream stream, long noOfEntries, long sizeEntries, long centralDirOffset, CancellationToken cancellationToken) { await stream.WriteProcToStreamAsync(s => WriteZip64EndOfCentralDirectory(s, noOfEntries, sizeEntries, centralDirOffset), cancellationToken).ConfigureAwait(false); @@ -234,11 +241,11 @@ internal static void WriteZip64EndOfCentralDirectory(Stream stream, long noOfEnt } /// - public static async Task WriteEndOfCentralDirectoryAsync(Stream stream, long noOfEntries, long sizeEntries, - long start, byte[] comment, CancellationToken cancellationToken) - => await stream.WriteProcToStreamAsync(s + public static async Task WriteEndOfCentralDirectoryAsync(Stream stream, long noOfEntries, long sizeEntries, + long start, byte[] comment, CancellationToken cancellationToken) + => await stream.WriteProcToStreamAsync(s => WriteEndOfCentralDirectory(s, noOfEntries, sizeEntries, start, comment), cancellationToken).ConfigureAwait(false); - + /// /// Write the required records to end the central directory. /// @@ -251,8 +258,8 @@ public static async Task WriteEndOfCentralDirectoryAsync(Stream stream, long no internal static void WriteEndOfCentralDirectory(Stream stream, long noOfEntries, long sizeEntries, long start, byte[] comment) { if (noOfEntries >= 0xffff || - start >= 0xffffffff || - sizeEntries >= 0xffffffff) + start >= 0xffffffff || + sizeEntries >= 0xffffffff) { WriteZip64EndOfCentralDirectory(stream, noOfEntries, sizeEntries, start); } @@ -459,7 +466,7 @@ internal static int WriteEndEntry(Stream stream, ZipEntry entry, StringCodec str byte[] extra = ed.GetEntryData(); byte[] entryComment = !(entry.Comment is null) - ? stringCodec.ZipOutputEncoding.GetBytes(entry.Comment) + ? stringCodec.ZipOutputEncoding.GetBytes(entry.Comment) : Empty.Array(); if (entryComment.Length > 0xffff) @@ -533,11 +540,11 @@ internal static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) extraData.AddNewEntry(0x9901); } - internal static async Task PatchLocalHeaderAsync(Stream stream, ZipEntry entry, + internal static async Task PatchLocalHeaderAsync(Stream stream, ZipEntry entry, EntryPatchData patchData, CancellationToken ct) { var initialPos = stream.Position; - + // Update CRC stream.Seek(patchData.CrcPatchOffset, SeekOrigin.Begin); await stream.WriteLEIntAsync((int)entry.Crc, ct).ConfigureAwait(false); diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs index 0cf7395cb..f52deebb9 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs @@ -1,9 +1,9 @@ -using ICSharpCode.SharpZipLib.Zip; +using ICSharpCode.SharpZipLib.Tests.TestSupport; +using ICSharpCode.SharpZipLib.Zip; using NUnit.Framework; using System; using System.IO; using System.Text; -using ICSharpCode.SharpZipLib.Tests.TestSupport; using System.Threading.Tasks; using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does; @@ -117,7 +117,7 @@ public void ZipFileAesRead() { var password = "password"; - using (var ms = new SingleByteReadingStream()) + using (var ms = new MemoryStream() /* SingleByteReadingStream */) { WriteEncryptedZipToStream(ms, password, 256); ms.Seek(0, SeekOrigin.Begin); @@ -537,7 +537,7 @@ public void WriteEncryptedZipToStream(Stream stream, int entryCount, string pass zs.SetLevel(9); // 0-9, 9 being the highest level of compression zs.Password = password; // optional. Null is the same as not setting. Required if using AES. - for (int i = 0; i < entryCount; i++) + for (int i = 0; i < entryCount; i++) { AddEncrypedEntryToStream(zs, $"test-{i}", keySize, compressionMethod); }