Skip to content

Commit 933c41c

Browse files
committed
PR #475: Fix updating zips with descriptor entries
- Unit tests for using ZipFile to update file entries that have descriptors - Fix size calculation in GetDescriptorSize - Handle optional descriptor signature when updating
1 parent ddc545b commit 933c41c

File tree

2 files changed

+154
-42
lines changed

2 files changed

+154
-42
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs

+71-42
Original file line numberDiff line numberDiff line change
@@ -1003,16 +1003,19 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
10031003
if (this[entryIndex].Crc != data.Crc)
10041004
{
10051005
status.AddError();
1006+
resultHandler?.Invoke(status, "Descriptor CRC mismatch");
10061007
}
10071008

10081009
if (this[entryIndex].CompressedSize != data.CompressedSize)
10091010
{
10101011
status.AddError();
1012+
resultHandler?.Invoke(status, "Descriptor compressed size mismatch");
10111013
}
10121014

10131015
if (this[entryIndex].Size != data.Size)
10141016
{
10151017
status.AddError();
1018+
resultHandler?.Invoke(status, "Descriptor size mismatch");
10161019
}
10171020
}
10181021
}
@@ -1921,11 +1924,9 @@ public void Modify(ZipEntry original, ZipEntry updated)
19211924
if ( original == null ) {
19221925
throw new ArgumentNullException("original");
19231926
}
1924-
19251927
if ( updated == null ) {
19261928
throw new ArgumentNullException("updated");
19271929
}
1928-
19291930
CheckUpdating();
19301931
contentsEdited_ = true;
19311932
updates_.Add(new ZipUpdate(original, updated));
@@ -2386,26 +2387,37 @@ private byte[] GetBuffer()
23862387

23872388
private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
23882389
{
2389-
int bytesToCopy = GetDescriptorSize(update);
2390+
// Don't include the signature size to allow copy without seeking
2391+
var bytesToCopy = GetDescriptorSize(update, false);
2392+
2393+
// Don't touch the source stream if no descriptor is present
2394+
if (bytesToCopy == 0) return;
23902395

2391-
if (bytesToCopy > 0)
2396+
var buffer = GetBuffer();
2397+
2398+
// Copy the first 4 bytes of the descriptor
2399+
source.Read(buffer, 0, sizeof(int));
2400+
dest.Write(buffer, 0, sizeof(int));
2401+
2402+
if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature)
23922403
{
2393-
byte[] buffer = GetBuffer();
2404+
// The initial bytes wasn't the descriptor, reduce the pending byte count
2405+
bytesToCopy -= buffer.Length;
2406+
}
23942407

2395-
while (bytesToCopy > 0)
2396-
{
2397-
int readSize = Math.Min(buffer.Length, bytesToCopy);
2408+
while (bytesToCopy > 0)
2409+
{
2410+
int readSize = Math.Min(buffer.Length, bytesToCopy);
23982411

2399-
int bytesRead = source.Read(buffer, 0, readSize);
2400-
if (bytesRead > 0)
2401-
{
2402-
dest.Write(buffer, 0, bytesRead);
2403-
bytesToCopy -= bytesRead;
2404-
}
2405-
else
2406-
{
2407-
throw new ZipException("Unxpected end of stream");
2408-
}
2412+
int bytesRead = source.Read(buffer, 0, readSize);
2413+
if (bytesRead > 0)
2414+
{
2415+
dest.Write(buffer, 0, bytesRead);
2416+
bytesToCopy -= bytesRead;
2417+
}
2418+
else
2419+
{
2420+
throw new ZipException("Unxpected end of stream");
24092421
}
24102422
}
24112423
}
@@ -2464,32 +2476,37 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
24642476
/// Get the size of the source descriptor for a <see cref="ZipUpdate"/>.
24652477
/// </summary>
24662478
/// <param name="update">The update to get the size for.</param>
2467-
/// <returns>The descriptor size, zero if there isnt one.</returns>
2468-
private int GetDescriptorSize(ZipUpdate update)
2479+
/// <param name="includingSignature">Whether to include the signature size</param>
2480+
/// <returns>The descriptor size, zero if there isn't one.</returns>
2481+
private int GetDescriptorSize(ZipUpdate update, bool includingSignature)
24692482
{
2470-
int result = 0;
2471-
if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
2472-
{
2473-
result = ZipConstants.DataDescriptorSize - 4;
2474-
if (update.Entry.LocalHeaderRequiresZip64)
2475-
{
2476-
result = ZipConstants.Zip64DataDescriptorSize - 4;
2477-
}
2478-
}
2479-
return result;
2483+
if (!((GeneralBitFlags)update.Entry.Flags).HasFlag(GeneralBitFlags.Descriptor))
2484+
return 0;
2485+
2486+
var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64
2487+
? ZipConstants.Zip64DataDescriptorSize
2488+
: ZipConstants.DataDescriptorSize;
2489+
2490+
return includingSignature
2491+
? descriptorWithSignature
2492+
: descriptorWithSignature - sizeof(int);
24802493
}
24812494

24822495
private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
24832496
{
2484-
int bytesToCopy = GetDescriptorSize(update);
2497+
var buffer = GetBuffer(); ;
2498+
2499+
stream.Position = sourcePosition;
2500+
stream.Read(buffer, 0, sizeof(int));
2501+
var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature;
2502+
2503+
var bytesToCopy = GetDescriptorSize(update, sourceHasSignature);
24852504

24862505
while (bytesToCopy > 0)
24872506
{
2488-
var readSize = (int)bytesToCopy;
2489-
byte[] buffer = GetBuffer();
2490-
24912507
stream.Position = sourcePosition;
2492-
int bytesRead = stream.Read(buffer, 0, readSize);
2508+
2509+
var bytesRead = stream.Read(buffer, 0, bytesToCopy);
24932510
if (bytesRead > 0)
24942511
{
24952512
stream.Position = destinationPosition;
@@ -2500,7 +2517,7 @@ private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long
25002517
}
25012518
else
25022519
{
2503-
throw new ZipException("Unxpected end of stream");
2520+
throw new ZipException("Unexpected end of stream");
25042521
}
25052522
}
25062523
}
@@ -2757,6 +2774,7 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
27572774

27582775
// Clumsy way of handling retrieving the original name and extra data length for now.
27592776
// TODO: Stop re-reading name and data length in CopyEntryDirect.
2777+
27602778
uint nameLength = ReadLEUshort();
27612779
uint extraLength = ReadLEUshort();
27622780

@@ -2765,14 +2783,25 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
27652783
if (skipOver)
27662784
{
27672785
if (update.OffsetBasedSize != -1)
2786+
{
27682787
destinationPosition += update.OffsetBasedSize;
2788+
}
27692789
else
2770-
// TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) archives.
2771-
// WinZip produces a warning on these entries:
2772-
// "caution: value of lrec.csize (compressed size) changed from ..."
2773-
destinationPosition +=
2774-
(sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
2775-
update.Entry.CompressedSize + GetDescriptorSize(update);
2790+
{
2791+
// Skip entry header
2792+
destinationPosition += (sourcePosition - entryDataOffset) + NameLengthOffset;
2793+
2794+
// Skip entry compressed data
2795+
destinationPosition += update.Entry.CompressedSize;
2796+
2797+
// Seek to end of entry to check for descriptor signature
2798+
baseStream_.Seek(destinationPosition, SeekOrigin.Begin);
2799+
2800+
var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature;
2801+
2802+
// Skip descriptor and it's signature (if present)
2803+
destinationPosition += GetDescriptorSize(update, descriptorHasSignature);
2804+
}
27762805
}
27772806
else
27782807
{

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

+83
Original file line numberDiff line numberDiff line change
@@ -1755,5 +1755,88 @@ public void ShouldReadAESBZip2ZipCreatedBy7Zip()
17551755
}
17561756
}
17571757
}
1758+
1759+
/// <summary>
1760+
/// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when deleting items in a zip
1761+
/// </summary>
1762+
/// <param name="useZip64">Whether Zip64 should be used in the test archive</param>
1763+
[TestCase(UseZip64.On)]
1764+
[TestCase(UseZip64.Off)]
1765+
[Category("Zip")]
1766+
public void TestDescriptorUpdateOnDelete(UseZip64 useZip64)
1767+
{
1768+
MemoryStream msw = new MemoryStreamWithoutSeek();
1769+
using (ZipOutputStream outStream = new ZipOutputStream(msw))
1770+
{
1771+
outStream.UseZip64 = useZip64;
1772+
outStream.IsStreamOwner = false;
1773+
outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
1774+
outStream.WriteByte(89);
1775+
1776+
outStream.PutNextEntry(new ZipEntry("StripedMarlin2"));
1777+
outStream.WriteByte(91);
1778+
}
1779+
1780+
var zipData = msw.ToArray();
1781+
Assert.IsTrue(ZipTesting.TestArchive(zipData));
1782+
1783+
using (var memoryStream = new MemoryStream(zipData))
1784+
{
1785+
using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
1786+
{
1787+
zipFile.BeginUpdate();
1788+
zipFile.Delete("StripedMarlin");
1789+
zipFile.CommitUpdate();
1790+
}
1791+
1792+
memoryStream.Position = 0;
1793+
1794+
using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
1795+
{
1796+
Assert.That(zipFile.TestArchive(true), Is.True);
1797+
}
1798+
}
1799+
}
1800+
1801+
/// <summary>
1802+
/// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when adding items to a zip
1803+
/// </summary>
1804+
/// <param name="useZip64">Whether Zip64 should be used in the test archive</param>
1805+
[TestCase(UseZip64.On)]
1806+
[TestCase(UseZip64.Off)]
1807+
[Category("Zip")]
1808+
public void TestDescriptorUpdateOnAdd(UseZip64 useZip64)
1809+
{
1810+
MemoryStream msw = new MemoryStreamWithoutSeek();
1811+
using (ZipOutputStream outStream = new ZipOutputStream(msw))
1812+
{
1813+
outStream.UseZip64 = useZip64;
1814+
outStream.IsStreamOwner = false;
1815+
outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
1816+
outStream.WriteByte(89);
1817+
}
1818+
1819+
var zipData = msw.ToArray();
1820+
Assert.IsTrue(ZipTesting.TestArchive(zipData));
1821+
1822+
using (var memoryStream = new MemoryStream())
1823+
{
1824+
memoryStream.Write(zipData, 0, zipData.Length);
1825+
1826+
using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
1827+
{
1828+
zipFile.BeginUpdate();
1829+
zipFile.Add(new StringMemoryDataSource("stripey"), "Zebra");
1830+
zipFile.CommitUpdate();
1831+
}
1832+
1833+
memoryStream.Position = 0;
1834+
1835+
using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
1836+
{
1837+
Assert.That(zipFile.TestArchive(true), Is.True);
1838+
}
1839+
}
1840+
}
17581841
}
17591842
}

0 commit comments

Comments
 (0)