diff --git a/LibZipSharp.UnitTest/ZipTests.cs b/LibZipSharp.UnitTest/ZipTests.cs
index f5b2b2fd..fba04d95 100644
--- a/LibZipSharp.UnitTest/ZipTests.cs
+++ b/LibZipSharp.UnitTest/ZipTests.cs
@@ -257,6 +257,85 @@ public void CheckForUnknownCompressionMethods ()
}
}
+ [Test]
+ public void SimulateXamarinAndroidUsage ([Values (true, false)] bool copyArchive, [Values (true, false)] bool useFiles)
+ {
+ string filePath = Path.GetFullPath ("packaged_resources");
+ if (!File.Exists (filePath)) {
+ filePath = Path.GetFullPath (Path.Combine ("LibZipSharp.UnitTest", "packaged_resources"));
+ }
+
+ string baseArchive = $"base_{copyArchive}_{useFiles}.zip";
+
+ if (File.Exists (baseArchive))
+ File.Delete (baseArchive);
+
+ var mode = FileMode.Create;
+ if (copyArchive) {
+ File.Copy (filePath, baseArchive);
+ mode = FileMode.Open;
+ }
+
+ List<(string name, CompressionMethod c, ulong size)> expectedFiles = new List<(string name, CompressionMethod c, ulong size)> ();
+ // use a specific seed so we always generate the same files
+ Random rnd = new Random (3456464);
+
+ using (var baseZip = new ZipWrapper (baseArchive, mode)) {
+ using (var zip = ZipArchive.Open (filePath, FileMode.Open)) {
+ foreach (var entry in zip) {
+ var entryName = entry.FullName;
+ if (entryName.Contains ("\\")) {
+ entryName = entryName.Replace ('\\', '/');
+ }
+ expectedFiles.Add ((entryName, entry.CompressionMethod, entry.Size));
+ if (copyArchive)
+ continue;
+ var ms = new MemoryStream ();
+ entry.Extract (ms);
+ TestContext.Out.WriteLine ($"Adding {entryName} to base.zip");
+ baseZip.Archive.AddStream (ms, entryName, compressionMethod: entry.CompressionMethod);
+ }
+ }
+
+ baseZip.FixupWindowsPathSeparators ((a, b) => TestContext.Out.WriteLine ($"Fixing up malformed entry `{a}` -> `{b}`"));
+
+ for (int i=0; i< 200; i++) {
+ uint fileSize = (uint)rnd.Next (341, 3535592);
+ byte[] buffer = new byte[fileSize];
+ rnd.NextBytes (buffer);
+ CompressionMethod compression = rnd.NextDouble () < 0.8 ? CompressionMethod.Deflate : CompressionMethod.Store;
+ string entryName = $"temp/file_{i}_size_{fileSize}_{compression}.bin";
+ string archivePath = rnd.NextDouble () < 0.3 ? entryName : Path.GetFileName (entryName);
+ TestContext.Out.WriteLine ($"Adding {entryName} to base.zip");
+ if (useFiles) {
+ Directory.CreateDirectory ("temp");
+ File.WriteAllBytes (entryName, buffer);
+ baseZip.Archive.AddFile (entryName, archivePath, compressionMethod: compression);
+ } else {
+ var ms = new MemoryStream (buffer);
+ ms.Position = 0;
+ baseZip.Archive.AddStream (ms, archivePath, compressionMethod: compression);
+ }
+ expectedFiles.Add ((archivePath, compression, fileSize));
+
+ if (rnd.NextDouble () < 0.2)
+ baseZip.Flush ();
+ }
+
+ }
+ using (var zip = ZipArchive.Open (baseArchive, FileMode.Open, strictConsistencyChecks: true)) {
+ foreach (var file in expectedFiles) {
+ Assert.IsTrue (zip.ContainsEntry (file.name), $"zip should contain {file}.");
+ var e = zip.ReadEntry(file.name);
+ Assert.AreEqual (file.size, e.Size, $"{file} should be {file.size} but was {e.Size}.");
+ Assert.AreEqual (file.c, e.CompressionMethod, $"{file} should be {file.c} but was {e.CompressionMethod}.");
+ }
+ }
+
+ if (File.Exists (baseArchive))
+ File.Delete (baseArchive);
+ }
+
[Test]
public void InMemoryZipFile ()
{
diff --git a/LibZipSharp.UnitTest/ZipWrapper.cs b/LibZipSharp.UnitTest/ZipWrapper.cs
new file mode 100644
index 00000000..23bbbd2f
--- /dev/null
+++ b/LibZipSharp.UnitTest/ZipWrapper.cs
@@ -0,0 +1,65 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using Xamarin.Tools.Zip;
+using Unix = Mono.Unix;
+
+namespace Tests {
+ public class ZipWrapper : IDisposable {
+ ZipArchive archive;
+ string filename;
+ public ZipArchive Archive => archive;
+
+ public ZipWrapper (string file, FileMode mode = FileMode.CreateNew) {
+ filename = file;
+ archive = ZipArchive.Open (filename, mode);
+ }
+
+ public void Flush () {
+ if (archive != null) {
+ archive.Close ();
+ archive.Dispose ();
+ archive = null;
+ }
+ archive = ZipArchive.Open (filename, FileMode.Open);
+ }
+
+ ///
+ /// HACK: aapt2 is creating zip entries on Windows such as `assets\subfolder/asset2.txt`
+ ///
+ public void FixupWindowsPathSeparators (Action onRename)
+ {
+ bool modified = false;
+ foreach (var entry in archive) {
+ if (entry.FullName.Contains ("\\")) {
+ var name = entry.FullName.Replace ('\\', '/');
+ onRename?.Invoke (entry.FullName, name);
+ entry.Rename (name);
+ modified = true;
+ }
+ }
+ if (modified) {
+ Flush ();
+ }
+ }
+
+ public void Dispose ()
+ {
+ Dispose(true);
+ GC.SuppressFinalize (this);
+ }
+
+ protected virtual void Dispose(bool disposing) {
+ if (disposing) {
+ if (archive != null) {
+ archive.Close ();
+ archive.Dispose ();
+ archive = null;
+ }
+ }
+ }
+ }
+}
diff --git a/LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs b/LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs
index e9ecceab..9be642ca 100644
--- a/LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs
+++ b/LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs
@@ -852,8 +852,14 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
length = (int)Math.Min (stream.Length - stream.Position, length);
buffer = ArrayPool.Shared.Rent (length);
try {
- int bytesRead = stream.Read (buffer, 0, length);
- Marshal.Copy (buffer, 0, data, bytesRead);
+ int bytesRead = 0;
+ int startIndex = 0;
+ while (length > 0) {
+ bytesRead = stream.Read (buffer, 0, length);
+ Marshal.Copy (buffer, startIndex, data, bytesRead);
+ startIndex += bytesRead;
+ length -= bytesRead;
+ }
return bytesRead;
} finally {
ArrayPool.Shared.Return (buffer);