diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs index 7fc1c5592..41b83bef5 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs @@ -398,9 +398,9 @@ private bool HaveKeys /// /// The file doesn't contain a valid zip archive. /// - public ZipFile(string name) : - this(name, null) - { + public ZipFile(string name) : + this(name, null) + { } @@ -534,10 +534,10 @@ public ZipFile(Stream stream) : /// /// The stream argument is null. /// - public ZipFile(Stream stream, bool leaveOpen) : - this(stream, leaveOpen, null) - { - + public ZipFile(Stream stream, bool leaveOpen) : + this(stream, leaveOpen, null) + { + } /// @@ -789,7 +789,8 @@ public Encoding ZipCryptoEncoding /// public StringCodec StringCodec { - set { + set + { _stringCodec = value; if (!isNewArchive_) { @@ -1182,7 +1183,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests) bool testData = (tests & HeaderTest.Extract) != 0; var entryAbsOffset = offsetOfFirstEntry + entry.Offset; - + baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin); var signature = (int)ReadLEUint(); @@ -1258,9 +1259,9 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests) throw new ZipException($"Version required to extract this entry not supported ({extractVersion})"); } - const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched - | GeneralBitFlags.StrongEncryption - | GeneralBitFlags.EnhancedCompress + const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched + | GeneralBitFlags.StrongEncryption + | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked; if (localFlags.HasAny(notSupportedFlags)) { @@ -1677,8 +1678,8 @@ public void CommitUpdate() { // Create an empty archive if none existed originally. if (entries_.Length != 0) return; - byte[] theComment = (newComment_ != null) - ? newComment_.RawComment + byte[] theComment = (newComment_ != null) + ? newComment_.RawComment : _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_); ZipFormat.WriteEndOfCentralDirectory(baseStream_, 0, 0, 0, theComment); } @@ -2165,7 +2166,12 @@ private void WriteLocalEntryHeader(ZipUpdate update) // No need to compress - no data. entry.CompressedSize = entry.Size; entry.Crc = 0; - entry.CompressionMethod = CompressionMethod.Stored; + + // Crypted files should be deflated, even with no data (such as directories) + if (!HaveKeys) + { + entry.CompressionMethod = CompressionMethod.Stored; + } } } else if (entry.CompressionMethod == CompressionMethod.Stored) @@ -2583,15 +2589,15 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source, /// The descriptor size, zero if there isn't one. private static int GetDescriptorSize(ZipUpdate update, bool includingSignature) { - if (!((GeneralBitFlags)update.Entry.Flags).HasAny(GeneralBitFlags.Descriptor)) + if (!((GeneralBitFlags)update.Entry.Flags).HasAny(GeneralBitFlags.Descriptor)) return 0; - - var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64 - ? ZipConstants.Zip64DataDescriptorSize + + var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64 + ? ZipConstants.Zip64DataDescriptorSize : ZipConstants.DataDescriptorSize; - return includingSignature - ? descriptorWithSignature + return includingSignature + ? descriptorWithSignature : descriptorWithSignature - sizeof(int); } @@ -2878,7 +2884,7 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin // Clumsy way of handling retrieving the original name and extra data length for now. // TODO: Stop re-reading name and data length in CopyEntryDirect. - + uint nameLength = ReadLEUshort(); uint extraLength = ReadLEUshort(); @@ -3013,7 +3019,7 @@ private void UpdateCommentOnly() } finally { - if(updateFile != baseStream_) + if (updateFile != baseStream_) updateFile.Dispose(); } @@ -3178,7 +3184,7 @@ private void RunUpdates() } byte[] theComment = newComment_?.RawComment ?? _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_); - ZipFormat.WriteEndOfCentralDirectory(workFile.baseStream_, updateCount_, + ZipFormat.WriteEndOfCentralDirectory(workFile.baseStream_, updateCount_, sizeEntries, centralDirOffset, theComment); endOfStream = workFile.baseStream_.Position; @@ -3520,7 +3526,7 @@ private ulong ReadLEUlong() #endregion Reading // NOTE this returns the offset of the first byte after the signature. - private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) + private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) => ZipFormat.LocateBlockWithSignature(baseStream_, signature, endLocation, minimumBlockSize, maximumVariableData); /// @@ -3578,14 +3584,14 @@ private void ReadEntries() } bool isZip64 = false; - + // Check if zip64 header information is required. bool requireZip64 = thisDiskNumber == 0xffff || - startCentralDirDisk == 0xffff || - entriesForThisDisk == 0xffff || - entriesForWholeCentralDir == 0xffff || - centralDirSize == 0xffffffff || - offsetOfCentralDir == 0xffffffff; + startCentralDirDisk == 0xffff || + entriesForThisDisk == 0xffff || + entriesForWholeCentralDir == 0xffff || + centralDirSize == 0xffffffff || + offsetOfCentralDir == 0xffffffff; // #357 - always check for the existence of the Zip64 central directory. // #403 - Take account of the fixed size of the locator when searching. @@ -3676,7 +3682,7 @@ private void ReadEntries() int extraLen = ReadLEUshort(); int commentLen = ReadLEUshort(); - + // ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream int diskStartNo = ReadLEUshort(); // ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream @@ -3773,9 +3779,9 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) int saltLen = entry.AESSaltLen; byte[] saltBytes = new byte[saltLen]; int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, offset: 0, saltLen); - + if (saltIn != saltLen) throw new ZipException($"AES Salt expected {saltLen} git {saltIn}"); - + byte[] pwdVerifyRead = new byte[2]; StreamUtils.ReadFully(baseStream, pwdVerifyRead); int blockSize = entry.AESKeySize / 8; // bits to bytes @@ -3819,7 +3825,7 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) { if (entry.Version >= ZipConstants.VersionStrongEncryption && - entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null; + entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null; var classicManaged = new PkzipClassicManaged(); diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs index f3bf9a995..bdc3af23b 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs @@ -832,5 +832,53 @@ public void PasswordCheckingWithDateInExtraData() Assert.AreEqual(checkTime.DateTime, uno.DateTime); } } + + [Test] + [Category("Zip")] + [Category("Encryption")] + public void ReadingEncryptedZipWithDirectories() + { + var ms = new MemoryStream(); + using (ZipOutputStream outStream = new ZipOutputStream(ms)) + { + outStream.IsStreamOwner = false; + outStream.Password = "testPassword123"; + outStream.SetLevel(5); + + var entry = new ZipEntry("Folder/"); + outStream.PutNextEntry(entry); + + entry = new ZipEntry("Folder/File.txt"); + outStream.PutNextEntry(entry); + byte[] fileData = Encoding.UTF8.GetBytes("This is some test data"); + outStream.Write(fileData, 0, fileData.Length); + } + + ms.Seek(0, SeekOrigin.Begin); + using (ZipFile zipFile = new ZipFile(ms)) + { + zipFile.BeginUpdate(); + zipFile.IsStreamOwner = false; + zipFile.Password = "testPassword123"; + zipFile.AddDirectory("Folder2/"); + zipFile.Add(new MemoryDataSource(Encoding.UTF8.GetBytes("Test content")), "Folder2/File2.txt"); + zipFile.CommitUpdate(); + } + + ms.Seek(0, SeekOrigin.Begin); + using (var inStream = new ZipInputStream(ms)) + { + inStream.IsStreamOwner = false; + inStream.Password = "testPassword123"; + ZipEntry fileEntry = inStream.GetNextEntry(); + Assert.AreEqual("Folder/", fileEntry.Name); + fileEntry = inStream.GetNextEntry(); + Assert.AreEqual("Folder/File.txt", fileEntry.Name); + fileEntry = inStream.GetNextEntry(); + Assert.AreEqual("Folder2/", fileEntry.Name); + fileEntry = inStream.GetNextEntry(); + Assert.AreEqual("Folder2/File2.txt", fileEntry.Name); + } + } } }