Skip to content

Commit 2df5b16

Browse files
Bret Johnsonjonpryor
andauthored
Fix enumerating zip with deleted entries (#53)
Previously, if a zip had deleted entries then enumerating the entries in the zip would throw an exception - the ReadEntry call on a deleted entry would thrown an exception. With this change, deleted entries are instead skipped in the enumeration. Co-authored-by: Jonathan Pryor <jonpryor@vt.edu>
1 parent a042554 commit 2df5b16

File tree

3 files changed

+103
-6
lines changed

3 files changed

+103
-6
lines changed

LibZipSharp.UnitTest/ZipTests.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using NUnit.Framework;
22
using System;
3+
using System.Collections.Generic;
34
using System.IO;
45
using System.Text;
56
using System.Threading;
@@ -157,5 +158,64 @@ public void SmallTextFile ()
157158
}
158159
}
159160
}
161+
162+
[TestCase (false)]
163+
[TestCase (true)]
164+
public void EnumerateSkipDeletedEntries (bool deleteFromExistingFile)
165+
{
166+
var ms = new MemoryStream (Encoding.UTF8.GetBytes(TEXT));
167+
File.WriteAllText ("file1.txt", "1111");
168+
string filePath = Path.GetFullPath ("file1.txt");
169+
if (File.Exists ("test-archive-write.zip"))
170+
File.Delete ("test-archive-write.zip");
171+
172+
ZipArchive zip = null;
173+
try {
174+
zip = ZipArchive.Open ("test-archive-write.zip", FileMode.CreateNew);
175+
176+
ZipEntry e;
177+
e = zip.AddFile (filePath, "/path/ZipTestCopy1.exe");
178+
e = zip.AddFile (filePath, "/path/ZipTestCopy2.exe");
179+
var text = "Hello World";
180+
e = zip.AddEntry ("/data/foo1.txt", text, Encoding.UTF8, CompressionMethod.Store);
181+
e = zip.AddEntry ("/data/foo2.txt", File.OpenRead(filePath), CompressionMethod.Store);
182+
183+
if (deleteFromExistingFile) {
184+
zip.Close ();
185+
zip = ZipArchive.Open ("test-archive-write.zip", FileMode.Open);
186+
}
187+
188+
ValidateEnumeratedEntries (zip, "path/ZipTestCopy1.exe", "path/ZipTestCopy2.exe", "data/foo1.txt", "data/foo2.txt");
189+
190+
// Delete first
191+
zip.DeleteEntry ("path/ZipTestCopy1.exe");
192+
ValidateEnumeratedEntries (zip, "path/ZipTestCopy2.exe", "data/foo1.txt", "data/foo2.txt");
193+
194+
// Delete last
195+
zip.DeleteEntry ("data/foo2.txt");
196+
ValidateEnumeratedEntries (zip, "path/ZipTestCopy2.exe", "data/foo1.txt");
197+
198+
// Delete middle
199+
zip.DeleteEntry ("path/ZipTestCopy2.exe");
200+
ValidateEnumeratedEntries (zip, "data/foo1.txt");
201+
202+
// Delete all
203+
zip.DeleteEntry ("data/foo1.txt");
204+
ValidateEnumeratedEntries (zip);
205+
}
206+
finally {
207+
zip?.Dispose ();
208+
}
209+
}
210+
211+
void ValidateEnumeratedEntries (ZipArchive zip, params string[] expectedEntries)
212+
{
213+
var actualEntries = new List<string>();
214+
foreach (var entry in zip) {
215+
actualEntries.Add (entry.FullName);
216+
}
217+
218+
Assert.AreEqual (expectedEntries, actualEntries.ToArray ());
219+
}
160220
}
161-
}
221+
}

ZipArchive.cs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,8 @@ public ZipEntry AddFileToDirectory (string sourcePath, string archiveDirectory =
368368
string destDir = NormalizeArchivePath (true, archiveDirectory);
369369
string destFile = useFileDirectory ? GetRootlessPath (sourcePath) : Path.GetFileName (sourcePath);
370370
return AddFile (sourcePath,
371-
String.IsNullOrEmpty (destDir) ? null : destDir + "/" + destFile,
372-
permissions, compressionMethod, overwriteExisting);
371+
String.IsNullOrEmpty (destDir) ? null : destDir + "/" + destFile,
372+
permissions, compressionMethod, overwriteExisting);
373373
}
374374

375375
/// <summary>
@@ -692,11 +692,40 @@ public ZipEntry ReadEntry (string entryName, bool caseSensitive = false)
692692
}
693693

694694
public ZipEntry ReadEntry (ulong index)
695+
{
696+
return ReadEntry (index, throwIfDeleted: true);
697+
}
698+
699+
/// <summary>
700+
/// Read a zip entry, given an index.
701+
///
702+
/// When throwIfDeleted is true, if the entry is deleted then an exception is thrown (the error will be
703+
/// ErrorCode.Deleted or ErrorCode.Inval, depending on whether the deleted entry previously existed in
704+
/// the zip or was newly added - that's just how libzip handles that). If throwIfDeleted is false then
705+
/// null is returned for deleted entries and an exception is just thrown for other errors.
706+
/// </summary>
707+
/// <param name="index">index to read</param>
708+
/// <param name="throwIfDeleted">whether to return null or throw an exception for deleted entries</param>
709+
/// <returns></returns>
710+
public ZipEntry ReadEntry (ulong index, bool throwIfDeleted)
695711
{
696712
Native.zip_stat_t stat;
697713
int ret = Native.zip_stat_index (archive, index, OperationFlags.None, out stat);
698-
if (ret < 0)
714+
if (ret < 0) {
715+
if (! throwIfDeleted) {
716+
IntPtr error = Native.zip_get_error (archive);
717+
718+
if (error != IntPtr.Zero) {
719+
int zip_error = Native.zip_error_code_zip (error);
720+
// Deleted is returned when the deleted entry existed when the zip was opened
721+
// Inval is returned when the deleted entry was newly added to the zip, then deleted
722+
if (zip_error == (int) ErrorCode.Deleted || zip_error == (int)ErrorCode.Inval)
723+
return null;
724+
}
725+
}
726+
699727
throw GetErrorException ();
728+
}
700729

701730
var ze = ZipEntry.Create (this, stat);
702731
ze.Init ();

ZipEntryEnumerator.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,15 @@ public bool MoveNext ()
6262

6363
// Calling it each time because the archive can change in the meantime
6464
long nentries = archive.EntryCount;
65-
if (nentries < 0 || index >= (ulong)nentries)
65+
if (nentries < 0)
66+
return false;
67+
68+
// Skip past any deleted entires
69+
while (index < (ulong)nentries && ReadEntry (index) == null) {
70+
++index;
71+
}
72+
73+
if (index >= (ulong)nentries)
6674
return false;
6775
return true;
6876
}
@@ -87,7 +95,7 @@ ZipEntry ReadEntry (ulong index)
8795
if (current != null && current.Index == index)
8896
return current;
8997

90-
current = archive.ReadEntry (index);
98+
current = archive.ReadEntry (index, throwIfDeleted:false);
9199
return current;
92100
}
93101
}

0 commit comments

Comments
 (0)