diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
index 02fd30778..5ae35f096 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
@@ -999,16 +999,19 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
if (this[entryIndex].Crc != data.Crc)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor CRC mismatch");
}
if (this[entryIndex].CompressedSize != data.CompressedSize)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor compressed size mismatch");
}
if (this[entryIndex].Size != data.Size)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor size mismatch");
}
}
}
@@ -1917,11 +1920,9 @@ public void Modify(ZipEntry original, ZipEntry updated)
if ( original == null ) {
throw new ArgumentNullException("original");
}
-
if ( updated == null ) {
throw new ArgumentNullException("updated");
}
-
CheckUpdating();
contentsEdited_ = true;
updates_.Add(new ZipUpdate(original, updated));
@@ -2049,8 +2050,14 @@ private void WriteLocalEntryHeader(ZipUpdate update)
// TODO: Local offset will require adjusting for multi-disk zip files.
entry.Offset = baseStream_.Position;
- // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
- if (update.Command != UpdateCommand.Copy)
+ // set flags based on the type of entry being written
+ if (update.Command == UpdateCommand.Copy)
+ {
+ // We're copying an existing entry during an update.
+ // In this case, we won't need descriptors as we should always know the size/CRC fields of the entry directly
+ entry.Flags &= ~(int)GeneralBitFlags.Descriptor;
+ }
+ else
{
if (entry.CompressionMethod == CompressionMethod.Deflated)
{
@@ -2382,26 +2389,37 @@ private byte[] GetBuffer()
private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
{
- int bytesToCopy = GetDescriptorSize(update);
+ // Don't include the signature size to allow copy without seeking
+ var bytesToCopy = GetDescriptorSize(update, false);
+
+ // Don't touch the source stream if no descriptor is present
+ if (bytesToCopy == 0) return;
- if (bytesToCopy > 0)
+ var buffer = GetBuffer();
+
+ // Copy the first 4 bytes of the descriptor
+ source.Read(buffer, 0, sizeof(int));
+ dest.Write(buffer, 0, sizeof(int));
+
+ if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature)
{
- byte[] buffer = GetBuffer();
+ // The initial bytes wasn't the descriptor, reduce the pending byte count
+ bytesToCopy -= buffer.Length;
+ }
- while (bytesToCopy > 0)
- {
- int readSize = Math.Min(buffer.Length, bytesToCopy);
+ while (bytesToCopy > 0)
+ {
+ int readSize = Math.Min(buffer.Length, bytesToCopy);
- int bytesRead = source.Read(buffer, 0, readSize);
- if (bytesRead > 0)
- {
- dest.Write(buffer, 0, bytesRead);
- bytesToCopy -= bytesRead;
- }
- else
- {
- throw new ZipException("Unxpected end of stream");
- }
+ int bytesRead = source.Read(buffer, 0, readSize);
+ if (bytesRead > 0)
+ {
+ dest.Write(buffer, 0, bytesRead);
+ bytesToCopy -= bytesRead;
+ }
+ else
+ {
+ throw new ZipException("Unxpected end of stream");
}
}
}
@@ -2460,32 +2478,37 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
/// Get the size of the source descriptor for a .
///
/// The update to get the size for.
- /// The descriptor size, zero if there isnt one.
- private int GetDescriptorSize(ZipUpdate update)
+ /// Whether to include the signature size
+ /// The descriptor size, zero if there isn't one.
+ private int GetDescriptorSize(ZipUpdate update, bool includingSignature)
{
- int result = 0;
- if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
- {
- result = ZipConstants.DataDescriptorSize - 4;
- if (update.Entry.LocalHeaderRequiresZip64)
- {
- result = ZipConstants.Zip64DataDescriptorSize - 4;
- }
- }
- return result;
+ if (!((GeneralBitFlags)update.Entry.Flags).HasFlag(GeneralBitFlags.Descriptor))
+ return 0;
+
+ var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64
+ ? ZipConstants.Zip64DataDescriptorSize
+ : ZipConstants.DataDescriptorSize;
+
+ return includingSignature
+ ? descriptorWithSignature
+ : descriptorWithSignature - sizeof(int);
}
private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
{
- int bytesToCopy = GetDescriptorSize(update);
+ var buffer = GetBuffer(); ;
+
+ stream.Position = sourcePosition;
+ stream.Read(buffer, 0, sizeof(int));
+ var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature;
+
+ var bytesToCopy = GetDescriptorSize(update, sourceHasSignature);
while (bytesToCopy > 0)
{
- var readSize = (int)bytesToCopy;
- byte[] buffer = GetBuffer();
-
stream.Position = sourcePosition;
- int bytesRead = stream.Read(buffer, 0, readSize);
+
+ var bytesRead = stream.Read(buffer, 0, bytesToCopy);
if (bytesRead > 0)
{
stream.Position = destinationPosition;
@@ -2496,7 +2519,7 @@ private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long
}
else
{
- throw new ZipException("Unxpected end of stream");
+ throw new ZipException("Unexpected end of stream");
}
}
}
@@ -2743,6 +2766,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();
@@ -2751,14 +2775,25 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
if (skipOver)
{
if (update.OffsetBasedSize != -1)
+ {
destinationPosition += update.OffsetBasedSize;
+ }
else
- // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) archives.
- // WinZip produces a warning on these entries:
- // "caution: value of lrec.csize (compressed size) changed from ..."
- destinationPosition +=
- (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
- update.Entry.CompressedSize + GetDescriptorSize(update);
+ {
+ // Skip entry header
+ destinationPosition += (sourcePosition - entryDataOffset) + NameLengthOffset;
+
+ // Skip entry compressed data
+ destinationPosition += update.Entry.CompressedSize;
+
+ // Seek to end of entry to check for descriptor signature
+ baseStream_.Seek(destinationPosition, SeekOrigin.Begin);
+
+ var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature;
+
+ // Skip descriptor and it's signature (if present)
+ destinationPosition += GetDescriptorSize(update, descriptorHasSignature);
+ }
}
else
{
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
index 7ae234523..e053bed04 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
@@ -1611,5 +1611,194 @@ public void AddFileWithAlternateName()
}
}
}
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when deleting items in a zip
+ ///
+ /// Whether Zip64 should be used in the test archive
+ /// Whether safe or direct updates should be used
+ [Test]
+ [Category("Zip")]
+ public void TestDescriptorUpdateOnDelete([Values] UseZip64 useZip64, [Values] FileUpdateMode updateMode)
+ {
+ MemoryStream msw = new MemoryStreamWithoutSeek();
+ using (ZipOutputStream outStream = new ZipOutputStream(msw))
+ {
+ outStream.UseZip64 = useZip64;
+ outStream.IsStreamOwner = false;
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
+ outStream.WriteByte(89);
+
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin2"));
+ outStream.WriteByte(91);
+ }
+
+ var zipData = msw.ToArray();
+ Assert.IsTrue(ZipTesting.TestArchive(zipData));
+
+ using (var memoryStream = new MemoryStream(zipData))
+ {
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate(new MemoryArchiveStorage(updateMode));
+ zipFile.Delete("StripedMarlin");
+ zipFile.CommitUpdate();
+
+ Assert.That(zipFile.TestArchive(true), Is.True);
+ }
+ }
+ }
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when adding items to a zip
+ ///
+ /// Whether Zip64 should be used in the test archive
+ /// Whether safe or direct updates should be used
+ [Test]
+ [Category("Zip")]
+ public void TestDescriptorUpdateOnAdd([Values] UseZip64 useZip64, [Values] FileUpdateMode updateMode)
+ {
+ MemoryStream msw = new MemoryStreamWithoutSeek();
+ using (ZipOutputStream outStream = new ZipOutputStream(msw))
+ {
+ outStream.UseZip64 = useZip64;
+ outStream.IsStreamOwner = false;
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
+ outStream.WriteByte(89);
+ }
+
+ var zipData = msw.ToArray();
+ Assert.IsTrue(ZipTesting.TestArchive(zipData));
+
+ using (var memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(zipData, 0, zipData.Length);
+
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate(new MemoryArchiveStorage(updateMode));
+ zipFile.Add(new StringMemoryDataSource("stripey"), "Zebra");
+ zipFile.CommitUpdate();
+
+ Assert.That(zipFile.TestArchive(true), Is.True);
+ }
+ }
+ }
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when replacing items in a zip
+ ///
+ /// Whether Zip64 should be used in the test archive
+ /// Whether safe or direct updates should be used
+ [Test]
+ [Category("Zip")]
+ public void TestDescriptorUpdateOnReplace([Values] UseZip64 useZip64, [Values] FileUpdateMode updateMode)
+ {
+ MemoryStream msw = new MemoryStreamWithoutSeek();
+ using (ZipOutputStream outStream = new ZipOutputStream(msw))
+ {
+ outStream.UseZip64 = useZip64;
+ outStream.IsStreamOwner = false;
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
+ outStream.WriteByte(89);
+ outStream.PutNextEntry(new ZipEntry("WhiteMarlin"));
+ outStream.WriteByte(91);
+ outStream.PutNextEntry(new ZipEntry("Sailfish"));
+ outStream.WriteByte(3);
+ }
+
+ var zipData = msw.ToArray();
+ Assert.IsTrue(ZipTesting.TestArchive(zipData));
+
+ using (var memoryStream = new MemoryStream(zipData))
+ {
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate(new MemoryArchiveStorage(updateMode));
+ zipFile.Delete("WhiteMarlin");
+ zipFile.Add(new StringMemoryDataSource("Kajikia albida"), "WhiteMarlin");
+ zipFile.CommitUpdate();
+
+ Assert.That(zipFile.TestArchive(true), Is.True);
+ Assert.That(zipFile.Count, Is.EqualTo(3));
+
+ // Test for expected descriptor states:
+ // 'StripedMarlin' should still have a descriptor in Direct update mode as the entry data will be kept, but won't have one
+ // in Safe update mode as that recreates the whole archive.
+ // 'WhiteMarlin' should no longer have one because the entry is new and didn't need one
+ // 'Sailfish' should have its descriptor removed.
+ var entry = zipFile.GetEntry("StripedMarlin");
+
+ if (updateMode == FileUpdateMode.Direct)
+ Assert.True(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+ else
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+
+ entry = zipFile.GetEntry("WhiteMarlin");
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+
+ entry = zipFile.GetEntry("Sailfish");
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+ }
+ }
+ }
+
+ // This is the initial zipfile generated by the 'TestDescriptorUpdateOnReplace' test, but with descriptor
+ // fields that don't have the signature bytes.
+ const string TestZipFileWithSignaturelessDescriptors = @"UEsDBBQACAAIAHO8E1EAAAAAAAAAAAAAAAANAAAA
+ U3RyaXBlZE1hcmxpbosEAN0GtcADAAAAAQAAAFBLAwQUAAgACABzvBNRAAAAAAAAAAAAAAAACwAAAFdoaXRlTWFybGlui
+ wYA8We7LgMAAAABAAAAUEsDBBQACAAIAHO8E1EAAAAAAAAAAAAAAAAIAAAAU2FpbGZpc2hjBgA3vgtLAwAAAAEAAABQSw
+ ECMwAUAAgACABzvBNR3Qa1wAMAAAABAAAADQAAAAAAAAAAAAAAAAAAAAAAU3RyaXBlZE1hcmxpblBLAQIzABQACAAIAHO
+ 8E1HxZ7suAwAAAAEAAAALAAAAAAAAAAAAAAAAADoAAABXaGl0ZU1hcmxpblBLAQIzABQACAAIAHO8E1E3vgtLAwAAAAEA
+ AAAIAAAAAAAAAAAAAAAAAHIAAABTYWlsZmlzaFBLBQYAAAAAAwADAKoAAACnAAAAAAA=";
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when replacing items in a zip, using a file whose descriptors
+ /// don't have signature bytes
+ ///
+ /// Whether Zip64 should be used in the test archive
+ /// Whether safe or direct updates should be used
+ [Test]
+ [Category("Zip")]
+ public void TestSignaturelessDescriptorUpdateOnReplace([Values] UseZip64 useZip64, [Values] FileUpdateMode updateMode)
+ {
+ var fileBytes = Convert.FromBase64String(TestZipFileWithSignaturelessDescriptors);
+
+ using (var memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(fileBytes, 0, fileBytes.Length);
+ memoryStream.Position = 0;
+
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate(new MemoryArchiveStorage(updateMode));
+ zipFile.Delete("WhiteMarlin");
+ zipFile.Add(new StringMemoryDataSource("Kajikia albida"), "WhiteMarlin");
+ zipFile.CommitUpdate();
+
+ // @@NOTE@@ TestArchive fails if an entry has a descriptor with no signature.
+ // Assert.That(zipFile.TestArchive(true), Is.True);
+ Assert.That(zipFile.Count, Is.EqualTo(3));
+
+ // Test for expected descriptor states:
+ // 'StripedMarlin' should still have a descriptor in Direct update mode as the entry data will be kept, but won't have one
+ // in Safe update mode as that recreates the whole archive.
+ // 'WhiteMarlin' should no longer have one because the entry is new and didn't need one
+ // 'Sailfish' should have its descriptor removed.
+ var entry = zipFile.GetEntry("StripedMarlin");
+
+ if (updateMode == FileUpdateMode.Direct)
+ Assert.True(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+ else
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+
+ entry = zipFile.GetEntry("WhiteMarlin");
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+
+ entry = zipFile.GetEntry("Sailfish");
+ Assert.False(((GeneralBitFlags)entry.Flags).HasFlag(GeneralBitFlags.Descriptor));
+ }
+ }
+ }
}
}