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

3 files changed

Lines changed: 103 additions & 6 deletions

File tree

‎LibZipSharp.UnitTest/ZipTests.cs‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
usingNUnit.Framework;
22
usingSystem;
3+
usingSystem.Collections.Generic;
34
usingSystem.IO;
45
usingSystem.Text;
56
usingSystem.Threading;
@@ -157,5 +158,64 @@ public void SmallTextFile ()
157158
}
158159
}
159160
}
161+
162+
[TestCase(false)]
163+
[TestCase(true)]
164+
publicvoidEnumerateSkipDeletedEntries(booldeleteFromExistingFile)
165+
{
166+
varms=newMemoryStream(Encoding.UTF8.GetBytes(TEXT));
167+
File.WriteAllText("file1.txt","1111");
168+
stringfilePath=Path.GetFullPath("file1.txt");
169+
if(File.Exists("test-archive-write.zip"))
170+
File.Delete("test-archive-write.zip");
171+
172+
ZipArchivezip=null;
173+
try{
174+
zip=ZipArchive.Open("test-archive-write.zip",FileMode.CreateNew);
175+
176+
ZipEntrye;
177+
e=zip.AddFile(filePath,"/path/ZipTestCopy1.exe");
178+
e=zip.AddFile(filePath,"/path/ZipTestCopy2.exe");
179+
vartext="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+
voidValidateEnumeratedEntries(ZipArchivezip,paramsstring[]expectedEntries)
212+
{
213+
varactualEntries=newList<string>();
214+
foreach(varentryinzip){
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
stringdestDir=NormalizeArchivePath(true,archiveDirectory);
369369
stringdestFile=useFileDirectory?GetRootlessPath(sourcePath):Path.GetFileName(sourcePath);
370370
returnAddFile(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
publicZipEntryReadEntry(ulongindex)
695+
{
696+
returnReadEntry(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+
publicZipEntryReadEntry(ulongindex,boolthrowIfDeleted)
695711
{
696712
Native.zip_stat_tstat;
697713
intret=Native.zip_stat_index(archive,index,OperationFlags.None,outstat);
698-
if(ret<0)
714+
if(ret<0){
715+
if(!throwIfDeleted){
716+
IntPtrerror=Native.zip_get_error(archive);
717+
718+
if(error!=IntPtr.Zero){
719+
intzip_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+
returnnull;
724+
}
725+
}
726+
699727
throwGetErrorException();
728+
}
700729

701730
varze=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
longnentries=archive.EntryCount;
65-
if(nentries<0||index>=(ulong)nentries)
65+
if(nentries<0)
66+
returnfalse;
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
returnfalse;
6775
returntrue;
6876
}
@@ -87,7 +95,7 @@ ZipEntry ReadEntry (ulong index)
8795
if(current!=null&&current.Index==index)
8896
returncurrent;
8997

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

0 commit comments

Comments
 (0)