Skip to content

Commit 3fddfae

Browse files
committed
Fix compression method for encrypted empty files and directories when updating a ZipFile
1 parent ff2d7c3 commit 3fddfae

File tree

2 files changed

+89
-35
lines changed

2 files changed

+89
-35
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,9 @@ private bool HaveKeys
398398
/// <exception cref="ZipException">
399399
/// The file doesn't contain a valid zip archive.
400400
/// </exception>
401-
public ZipFile(string name) :
402-
this(name, null)
403-
{
401+
public ZipFile(string name) :
402+
this(name, null)
403+
{
404404

405405
}
406406

@@ -534,10 +534,10 @@ public ZipFile(Stream stream) :
534534
/// <exception cref="ArgumentNullException">
535535
/// The <see cref="Stream">stream</see> argument is null.
536536
/// </exception>
537-
public ZipFile(Stream stream, bool leaveOpen) :
538-
this(stream, leaveOpen, null)
539-
{
540-
537+
public ZipFile(Stream stream, bool leaveOpen) :
538+
this(stream, leaveOpen, null)
539+
{
540+
541541
}
542542

543543
/// <summary>
@@ -789,7 +789,8 @@ public Encoding ZipCryptoEncoding
789789
/// <inheritdoc cref="Zip.StringCodec"/>
790790
public StringCodec StringCodec
791791
{
792-
set {
792+
set
793+
{
793794
_stringCodec = value;
794795
if (!isNewArchive_)
795796
{
@@ -1182,7 +1183,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
11821183
bool testData = (tests & HeaderTest.Extract) != 0;
11831184

11841185
var entryAbsOffset = offsetOfFirstEntry + entry.Offset;
1185-
1186+
11861187
baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin);
11871188
var signature = (int)ReadLEUint();
11881189

@@ -1258,9 +1259,9 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
12581259
throw new ZipException($"Version required to extract this entry not supported ({extractVersion})");
12591260
}
12601261

1261-
const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched
1262-
| GeneralBitFlags.StrongEncryption
1263-
| GeneralBitFlags.EnhancedCompress
1262+
const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched
1263+
| GeneralBitFlags.StrongEncryption
1264+
| GeneralBitFlags.EnhancedCompress
12641265
| GeneralBitFlags.HeaderMasked;
12651266
if (localFlags.HasAny(notSupportedFlags))
12661267
{
@@ -1677,8 +1678,8 @@ public void CommitUpdate()
16771678
{
16781679
// Create an empty archive if none existed originally.
16791680
if (entries_.Length != 0) return;
1680-
byte[] theComment = (newComment_ != null)
1681-
? newComment_.RawComment
1681+
byte[] theComment = (newComment_ != null)
1682+
? newComment_.RawComment
16821683
: _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_);
16831684
ZipFormat.WriteEndOfCentralDirectory(baseStream_, 0, 0, 0, theComment);
16841685
}
@@ -2165,7 +2166,12 @@ private void WriteLocalEntryHeader(ZipUpdate update)
21652166
// No need to compress - no data.
21662167
entry.CompressedSize = entry.Size;
21672168
entry.Crc = 0;
2168-
entry.CompressionMethod = CompressionMethod.Stored;
2169+
2170+
// Crypted files should be deflated, even with no data (such as directories)
2171+
if (!HaveKeys)
2172+
{
2173+
entry.CompressionMethod = CompressionMethod.Stored;
2174+
}
21692175
}
21702176
}
21712177
else if (entry.CompressionMethod == CompressionMethod.Stored)
@@ -2583,15 +2589,15 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
25832589
/// <returns>The descriptor size, zero if there isn't one.</returns>
25842590
private static int GetDescriptorSize(ZipUpdate update, bool includingSignature)
25852591
{
2586-
if (!((GeneralBitFlags)update.Entry.Flags).HasAny(GeneralBitFlags.Descriptor))
2592+
if (!((GeneralBitFlags)update.Entry.Flags).HasAny(GeneralBitFlags.Descriptor))
25872593
return 0;
2588-
2589-
var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64
2590-
? ZipConstants.Zip64DataDescriptorSize
2594+
2595+
var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64
2596+
? ZipConstants.Zip64DataDescriptorSize
25912597
: ZipConstants.DataDescriptorSize;
25922598

2593-
return includingSignature
2594-
? descriptorWithSignature
2599+
return includingSignature
2600+
? descriptorWithSignature
25952601
: descriptorWithSignature - sizeof(int);
25962602
}
25972603

@@ -2878,7 +2884,7 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
28782884

28792885
// Clumsy way of handling retrieving the original name and extra data length for now.
28802886
// TODO: Stop re-reading name and data length in CopyEntryDirect.
2881-
2887+
28822888
uint nameLength = ReadLEUshort();
28832889
uint extraLength = ReadLEUshort();
28842890

@@ -3013,7 +3019,7 @@ private void UpdateCommentOnly()
30133019
}
30143020
finally
30153021
{
3016-
if(updateFile != baseStream_)
3022+
if (updateFile != baseStream_)
30173023
updateFile.Dispose();
30183024
}
30193025

@@ -3178,7 +3184,7 @@ private void RunUpdates()
31783184
}
31793185

31803186
byte[] theComment = newComment_?.RawComment ?? _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_);
3181-
ZipFormat.WriteEndOfCentralDirectory(workFile.baseStream_, updateCount_,
3187+
ZipFormat.WriteEndOfCentralDirectory(workFile.baseStream_, updateCount_,
31823188
sizeEntries, centralDirOffset, theComment);
31833189

31843190
endOfStream = workFile.baseStream_.Position;
@@ -3520,7 +3526,7 @@ private ulong ReadLEUlong()
35203526
#endregion Reading
35213527

35223528
// NOTE this returns the offset of the first byte after the signature.
3523-
private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
3529+
private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
35243530
=> ZipFormat.LocateBlockWithSignature(baseStream_, signature, endLocation, minimumBlockSize, maximumVariableData);
35253531

35263532
/// <summary>
@@ -3578,14 +3584,14 @@ private void ReadEntries()
35783584
}
35793585

35803586
bool isZip64 = false;
3581-
3587+
35823588
// Check if zip64 header information is required.
35833589
bool requireZip64 = thisDiskNumber == 0xffff ||
3584-
startCentralDirDisk == 0xffff ||
3585-
entriesForThisDisk == 0xffff ||
3586-
entriesForWholeCentralDir == 0xffff ||
3587-
centralDirSize == 0xffffffff ||
3588-
offsetOfCentralDir == 0xffffffff;
3590+
startCentralDirDisk == 0xffff ||
3591+
entriesForThisDisk == 0xffff ||
3592+
entriesForWholeCentralDir == 0xffff ||
3593+
centralDirSize == 0xffffffff ||
3594+
offsetOfCentralDir == 0xffffffff;
35893595

35903596
// #357 - always check for the existence of the Zip64 central directory.
35913597
// #403 - Take account of the fixed size of the locator when searching.
@@ -3676,7 +3682,7 @@ private void ReadEntries()
36763682
int extraLen = ReadLEUshort();
36773683
int commentLen = ReadLEUshort();
36783684

3679-
3685+
36803686
// ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream
36813687
int diskStartNo = ReadLEUshort();
36823688
// 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)
37733779
int saltLen = entry.AESSaltLen;
37743780
byte[] saltBytes = new byte[saltLen];
37753781
int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, offset: 0, saltLen);
3776-
3782+
37773783
if (saltIn != saltLen) throw new ZipException($"AES Salt expected {saltLen} git {saltIn}");
3778-
3784+
37793785
byte[] pwdVerifyRead = new byte[2];
37803786
StreamUtils.ReadFully(baseStream, pwdVerifyRead);
37813787
int blockSize = entry.AESKeySize / 8; // bits to bytes
@@ -3819,7 +3825,7 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
38193825
private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
38203826
{
38213827
if (entry.Version >= ZipConstants.VersionStrongEncryption &&
3822-
entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null;
3828+
entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null;
38233829

38243830
var classicManaged = new PkzipClassicManaged();
38253831

test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,5 +832,53 @@ public void PasswordCheckingWithDateInExtraData()
832832
Assert.AreEqual(checkTime.DateTime, uno.DateTime);
833833
}
834834
}
835+
836+
[Test]
837+
[Category("Zip")]
838+
[Category("Encryption")]
839+
public void ReadingEncryptedZipWithDirectories()
840+
{
841+
var ms = new MemoryStream();
842+
using (ZipOutputStream outStream = new ZipOutputStream(ms))
843+
{
844+
outStream.IsStreamOwner = false;
845+
outStream.Password = "testPassword123";
846+
outStream.SetLevel(5);
847+
848+
var entry = new ZipEntry("Folder/");
849+
outStream.PutNextEntry(entry);
850+
851+
entry = new ZipEntry("Folder/File.txt");
852+
outStream.PutNextEntry(entry);
853+
byte[] fileData = Encoding.UTF8.GetBytes("This is some test data");
854+
outStream.Write(fileData, 0, fileData.Length);
855+
}
856+
857+
ms.Seek(0, SeekOrigin.Begin);
858+
using (ZipFile zipFile = new ZipFile(ms))
859+
{
860+
zipFile.BeginUpdate();
861+
zipFile.IsStreamOwner = false;
862+
zipFile.Password = "testPassword123";
863+
zipFile.AddDirectory("Folder2/");
864+
zipFile.Add(new MemoryDataSource(Encoding.UTF8.GetBytes("Test content")), "Folder2/File2.txt");
865+
zipFile.CommitUpdate();
866+
}
867+
868+
ms.Seek(0, SeekOrigin.Begin);
869+
using (var inStream = new ZipInputStream(ms))
870+
{
871+
inStream.IsStreamOwner = false;
872+
inStream.Password = "testPassword123";
873+
ZipEntry fileEntry = inStream.GetNextEntry();
874+
Assert.AreEqual("Folder/", fileEntry.Name);
875+
fileEntry = inStream.GetNextEntry();
876+
Assert.AreEqual("Folder/File.txt", fileEntry.Name);
877+
fileEntry = inStream.GetNextEntry();
878+
Assert.AreEqual("Folder2/", fileEntry.Name);
879+
fileEntry = inStream.GetNextEntry();
880+
Assert.AreEqual("Folder2/File2.txt", fileEntry.Name);
881+
}
882+
}
835883
}
836884
}

0 commit comments

Comments
 (0)