From bf4372aec20a1540487d7e0a67b0cae9b0713a0b Mon Sep 17 00:00:00 2001 From: Richard Webb Date: Fri, 7 Aug 2020 15:41:24 +0100 Subject: [PATCH] PR #460: Account for AES overhead in compressed entry size (#460) * Unit test for writing encrypted 0 byte files to ZipOutputStream * In ZipOutputStream.PutNextEntry, account for AES overhead when calculating compressed entry size --- src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs | 22 +++++++++++- .../Zip/ZipOutputStream.cs | 13 ++----- .../Zip/ZipEncryptionHandling.cs | 36 +++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs index 7d15b01d2..14f760286 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs @@ -655,7 +655,7 @@ public bool LocalHeaderRequiresZip64 if ((versionToExtract == 0) && IsCrypted) { - trueCompressedSize += ZipConstants.CryptoHeaderSize; + trueCompressedSize += (ulong)this.EncryptionOverheadSize; } // TODO: A better estimation of the true limit based on compression overhead should be used @@ -1013,6 +1013,26 @@ internal int AESOverheadSize } } + /// + /// Number of extra bytes required to hold the encryption header fields. + /// + internal int EncryptionOverheadSize + { + get + { + // Entry is not encrypted - no overhead + if (!this.IsCrypted) + return 0; + + // Entry is encrypted using ZipCrypto + if (_aesEncryptionStrength == 0) + return ZipConstants.CryptoHeaderSize; + + // Entry is encrypted using AES + return this.AESOverheadSize; + } + } + /// /// Process extra data fields updating the entry based on the contents. /// diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs index bfd308daa..fe5079e07 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs @@ -337,7 +337,7 @@ public void PutNextEntry(ZipEntry entry) } else { - WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); + WriteLeInt((int)entry.CompressedSize + entry.EncryptionOverheadSize); WriteLeInt((int)entry.Size); } } @@ -382,7 +382,7 @@ public void PutNextEntry(ZipEntry entry) if (headerInfoAvailable) { ed.AddLeLong(entry.Size); - ed.AddLeLong(entry.CompressedSize); + ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize); } else { @@ -540,14 +540,7 @@ public void CloseEntry() if (curEntry.IsCrypted) { - if (curEntry.AESKeySize > 0) - { - curEntry.CompressedSize += curEntry.AESOverheadSize; - } - else - { - curEntry.CompressedSize += ZipConstants.CryptoHeaderSize; - } + curEntry.CompressedSize += curEntry.EncryptionOverheadSize; } // Patch the header if possible diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs index f9c988df1..d77e309b0 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs @@ -42,6 +42,42 @@ public void ZipCryptoEncryption(CompressionMethod compressionMethod) CreateZipWithEncryptedEntries("foo", 0, compressionMethod); } + /// + /// Test Known zero length encrypted entries with ZipOutputStream. + /// These are entries where the entry size is set to 0 ahead of time, so that PutNextEntry will fill in the header and there will be no patching. + /// Test with Zip64 on and off, as the logic is different for the two. + /// + [Test] + public void ZipOutputStreamEncryptEmptyEntries( + [Values] UseZip64 useZip64, + [Values(0, 128, 256)] int keySize, + [Values(CompressionMethod.Stored, CompressionMethod.Deflated)] CompressionMethod compressionMethod) + { + using (var ms = new MemoryStream()) + { + using (var zipOutputStream = new ZipOutputStream(ms)) + { + zipOutputStream.IsStreamOwner = false; + zipOutputStream.Password = "password"; + zipOutputStream.UseZip64 = useZip64; + + ZipEntry zipEntry = new ZipEntry("emptyEntry") + { + AESKeySize = keySize, + CompressionMethod = compressionMethod, + CompressedSize = 0, + Crc = 0, + Size = 0, + }; + + zipOutputStream.PutNextEntry(zipEntry); + zipOutputStream.CloseEntry(); + } + + VerifyZipWith7Zip(ms, "password"); + } + } + [Test] [Category("Encryption")] [Category("Zip")]