Skip to content

Commit b541b87

Browse files
authored
Fix the elusive invalid zip archive issue that has been a problem for ages! (#142)
* Fix the elusive invalid zip archive issue that has been a problem for ages! Fixes dotnet/android#8988 We had this odd corrupt zip file issue which kept cropping up on our Azure Pipelines builds. We had no idea what caused it until now. Some of the data for the local headers of an item (not the central directory) would be written incorrectly. This would result in a zip which may or may not be extractable, it would depend on how resilient the software extracting the data would be. So, what was happening here was that (sometimes) libzip would start writing some data (most likely the local file header) using our stream source callback, and it would seek a few bytes into the data and then tried to seek back to the beginning. The latter seek was done by giving the seek operation of the callback an offset of 0 which, unfortunately, was also used by the code as a guard as to whether or not to even perform the seek operation. The effect was that we ignored the seek to 0 and the stream remained at whatever the previous seek location was requested, thus corrupting data. It happened only on the very first entry, since that was the only one which would have position 0 within its range. We discovered that just enabling the strict consistency checks would uncover the issue, so that has been enabled in a number of unit tests. Once we did that it turns out we were writting the corrupt data ALL the TIME!. Fixing up the seeking code to take into account that we might want to see to 0 fixed the issue. * Bump to 3.3.0 due to ABI changes
1 parent c2ae332 commit b541b87

File tree

5 files changed

+69
-41
lines changed

5 files changed

+69
-41
lines changed

.vscode/tasks.json

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
"label": "Build LibZipSharp",
88
"type": "shell",
9-
"command": "msbuild LibZipSharp/libZipSharp.csproj /restore /t:Build",
9+
"command": "dotnet build LibZipSharp/libZipSharp.csproj -t:Build",
1010
"group": {
1111
"kind": "build",
1212
"isDefault": true
@@ -18,7 +18,7 @@
1818
{
1919
"label": "Pack LibZipSharp Nuget",
2020
"type": "shell",
21-
"command": "msbuild LibZipSharp/libZipSharp.csproj /t:Pack",
21+
"command": "dotnet pack LibZipSharp/libZipSharp.csproj",
2222
"group": {
2323
"kind": "build",
2424
"isDefault": true
@@ -30,7 +30,7 @@
3030
{
3131
"label": "Clean LibZipSharp",
3232
"type": "shell",
33-
"command": "msbuild LibZipSharp/libZipSharp.csproj /restore /t:Clean",
33+
"command": "dotnet build LibZipSharp/libZipSharp.csproj -t:Clean",
3434
"group": {
3535
"kind": "build",
3636
"isDefault": true
@@ -42,7 +42,7 @@
4242
{
4343
"label": "Build LibZipSharp Unit Tests",
4444
"type": "shell",
45-
"command": "msbuild LibZipSharp.UnitTest/LibZipSharp.UnitTest.csproj /restore /t:Build /p:ReferenceNuget=False",
45+
"command": "dotnet build LibZipSharp.UnitTest/LibZipSharp.UnitTest.csproj -t:Build -p:ReferenceNuget=False",
4646
"group": {
4747
"kind": "build",
4848
"isDefault": true
@@ -51,17 +51,5 @@
5151
"$msCompile"
5252
]
5353
},
54-
{
55-
"label": "Run LibZipSharp Unit Tests",
56-
"type": "shell",
57-
"command": "msbuild LibZipSharp.UnitTest/LibZipSharp.UnitTest.csproj /restore /t:RunNunitTests /p:ReferenceNuget=False",
58-
"group": {
59-
"kind": "test",
60-
"isDefault": true
61-
},
62-
"problemMatcher": [
63-
"$msCompile"
64-
]
65-
}
6654
]
6755
}

LibZipSharp.UnitTest/ZipTests.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ public void InMemoryZipFile ()
345345
}
346346

347347
stream.Position = 0;
348-
using (var zip = ZipArchive.Open (stream)) {
348+
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true)) {
349349
Assert.AreEqual (3, zip.EntryCount);
350350
foreach (var e in zip) {
351351
Console.WriteLine (e.FullName);
@@ -354,7 +354,7 @@ public void InMemoryZipFile ()
354354
}
355355

356356
stream.Position = 0;
357-
using (var zip = ZipArchive.Open (stream)) {
357+
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true)) {
358358
Assert.AreEqual (2, zip.EntryCount);
359359
zip.AddEntry ("info.json", File.ReadAllText (filePath), Encoding.UTF8, CompressionMethod.Deflate);
360360
}
@@ -377,6 +377,40 @@ public void InMemoryZipFile ()
377377
}
378378
}
379379

380+
[Test]
381+
public void EnumerateOnEmptyThenAddFiles ()
382+
{
383+
File.WriteAllText ("file1.txt", TEXT);
384+
File.WriteAllText ("file2.txt", TEXT);
385+
string filePath = Path.GetFullPath ("file1.txt");
386+
if (File.Exists ("enumerateonempty.zip"))
387+
File.Delete ("enumerateonempty.zip");
388+
389+
390+
using (var stream = File.Create ("test-archive-write.zip"))
391+
using (var zip = ZipArchive.Create (stream)) {
392+
foreach (var entry in zip) {
393+
Console.Write (entry.FullName);
394+
}
395+
ZipEntry e;
396+
e = zip.AddFile (filePath, "/path/ZipTestCopy1.exe");
397+
filePath = Path.GetFullPath ("file2.txt");
398+
e = zip.AddFile (filePath, "/path/ZipTestCopy2.exe");
399+
400+
foreach (var entry in zip) {
401+
Console.Write (entry.FullName);
402+
}
403+
404+
zip.Close ();
405+
}
406+
407+
using (var zip = ZipArchive.Open ("test-archive-write.zip", FileMode.Open, strictConsistencyChecks: true)) {
408+
foreach (var entry in zip) {
409+
Console.Write (entry.FullName);
410+
}
411+
}
412+
}
413+
380414
[TestCase (false)]
381415
[TestCase (true)]
382416
public void EnumerateSkipDeletedEntries (bool deleteFromExistingFile)

LibZipSharp.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
22
<PropertyGroup>
33
<_LibZipSharpAssemblyVersionMajor>3</_LibZipSharpAssemblyVersionMajor>
4-
<_LibZipSharpAssemblyVersionMinor>2</_LibZipSharpAssemblyVersionMinor>
4+
<_LibZipSharpAssemblyVersionMinor>3</_LibZipSharpAssemblyVersionMinor>
55
<_LibZipSharpAssemblyVersionPatch>0</_LibZipSharpAssemblyVersionPatch>
66
<_LibZipSharpAssemblyVersion>$(_LibZipSharpAssemblyVersionMajor).$(_LibZipSharpAssemblyVersionMinor).$(_LibZipSharpAssemblyVersionPatch)</_LibZipSharpAssemblyVersion>
77
<_NativeLibraryVersionForName>$(_LibZipSharpAssemblyVersionMajor)-$(_LibZipSharpAssemblyVersionMinor)</_NativeLibraryVersionForName>

LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using System.Buffers;
2929
using System.Collections;
3030
using System.Collections.Generic;
31+
using System.Diagnostics;
3132
using System.IO;
3233
using System.Runtime.InteropServices;
3334
using System.Text;
@@ -195,19 +196,26 @@ ErrorCode Open (string path, OpenFlags flags)
195196
/// </summary>
196197
/// <param name="stream">The stream to open</param>
197198
/// <param name="options">Platform-specific options</param>
198-
public static ZipArchive Open (Stream stream, IPlatformOptions options = null)
199+
/// <param name="strictConsistencyChecks"></param>
200+
public static ZipArchive Open (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false)
199201
{
200-
return ZipArchive.CreateInstanceFromStream (stream, OpenFlags.None, options);
202+
OpenFlags flags = OpenFlags.None;
203+
if (strictConsistencyChecks)
204+
flags |= OpenFlags.CheckCons;
205+
return ZipArchive.CreateInstanceFromStream (stream, flags, options);
201206
}
202207

203208
/// <summary>
204209
/// Create a new archive using the Stream provided. The steam should be an empty stream, any existing data will be overwritten.
205210
/// </summary>
206211
/// <param name="stream">The stream to create the arhive in</param>
207212
/// <param name="options">Platform-specific options</param>
208-
public static ZipArchive Create (Stream stream, IPlatformOptions options = null)
213+
public static ZipArchive Create (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false)
209214
{
210-
return ZipArchive.CreateInstanceFromStream (stream, OpenFlags.Create | OpenFlags.Truncate, options);
215+
OpenFlags flags = OpenFlags.Create | OpenFlags.Truncate;
216+
if (strictConsistencyChecks)
217+
flags |= OpenFlags.CheckCons;
218+
return ZipArchive.CreateInstanceFromStream (stream, flags, options);
211219
}
212220

213221
/// <summary>
@@ -788,6 +796,7 @@ internal ZipException GetErrorException ()
788796
internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64 len, SourceCommand cmd)
789797
{
790798
byte [] buffer = null;
799+
Native.zip_error_t error;
791800
int length = (int)len;
792801
var handle = GCHandle.FromIntPtr (state);
793802
if (!handle.IsAllocated)
@@ -833,30 +842,26 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
833842
return -1;
834843
}
835844

836-
long firstOffset, secondOffset;
845+
SeekOrigin seek = Native.ConvertWhence (args.whence);
837846
if (args.offset > Int64.MaxValue) {
838847
// Stream.Seek uses a signed 64-bit value for the offset, we need to split it up
839-
firstOffset = Int64.MaxValue;
840-
secondOffset = (long)(args.offset - Int64.MaxValue);
848+
if (!Seek (destination, seek, Int64.MaxValue) || !Seek (destination, seek, (long)(args.offset - Int64.MaxValue))) {
849+
return -1;
850+
}
841851
} else {
842-
firstOffset = (long)args.offset;
843-
secondOffset = 0;
844-
}
845-
846-
SeekOrigin seek = Native.ConvertWhence (args.whence);
847-
Native.zip_error_t error;
848-
849-
if (!SeekIfNeeded (destination, seek, firstOffset) || !SeekIfNeeded (destination, seek, secondOffset)) {
850-
return -1;
852+
if (!Seek (destination, seek, (long)args.offset)) {
853+
return -1;
854+
}
851855
}
856+
852857
break;
853858

854859
case SourceCommand.Seek:
855860
long offset = Native.zip_source_seek_compute_offset ((UInt64)stream.Position, (UInt64)stream.Length, data, len, out error);
856861
if (offset < 0) {
857862
return offset;
858863
}
859-
if (offset != stream.Seek (offset, SeekOrigin.Begin)) {
864+
if (!Seek (stream, SeekOrigin.Begin, offset)) {
860865
return -1;
861866
}
862867
break;
@@ -962,12 +967,8 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
962967

963968
return 0;
964969

965-
bool SeekIfNeeded (Stream s, SeekOrigin origin, long offset)
970+
bool Seek (Stream s, SeekOrigin origin, long offset)
966971
{
967-
if (offset == 0) {
968-
return true;
969-
}
970-
971972
return s.Seek (offset, origin) == offset;
972973
}
973974
}

azure-pipelines.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ extends:
105105
archiveType: 7z
106106
replaceExistingArchive: true
107107
archiveFile: $(Build.ArtifactStagingDirectory)\libzip-windows-arm-x64.7z
108+
- script: |
109+
7z t $(Build.ArtifactStagingDirectory)\libzip-windows-arm-x64.7z
110+
7z t $(Build.ArtifactStagingDirectory)\libzip-windows-x64.7z
111+
7z t $(Build.ArtifactStagingDirectory)\libzip-windows-x86.7z
112+
displayName: Test Archives
108113
109114
- job: buildLinux
110115
pool:

0 commit comments

Comments
 (0)