diff --git a/src/ZipStorer.cs b/src/ZipStorer.cs
index 9d37642..28468c9 100644
--- a/src/ZipStorer.cs
+++ b/src/ZipStorer.cs
@@ -29,31 +29,31 @@ public enum Compression : ushort
public class ZipFileEntry
{
/// Compression method
- public Compression Method {get; set;}
+ public Compression Method { get; set; }
/// Full path and filename as stored in Zip
- public string FilenameInZip {get; set;}
+ public string FilenameInZip { get; set; }
/// Original file size
- public long FileSize {get; set;}
+ public long FileSize { get; set; }
/// Compressed file size
- public long CompressedSize {get; set;}
+ public long CompressedSize { get; set; }
/// Offset of header information inside Zip storage
- public long HeaderOffset {get; set;}
+ public long HeaderOffset { get; set; }
/// Offset of file inside Zip storage
- public long FileOffset {get; set;}
+ public long FileOffset { get; set; }
/// Size of header information
- public uint HeaderSize {get; set;}
+ public uint HeaderSize { get; set; }
/// 32-bit checksum of entire file
- public uint Crc32 {get; set;}
+ public uint Crc32 { get; set; }
/// Last modification time of file
- public DateTime ModifyTime {get; set;}
+ public DateTime ModifyTime { get; set; }
/// Creation time of file
- public DateTime CreationTime {get; set;}
+ public DateTime CreationTime { get; set; }
/// Last access time of file
- public DateTime AccessTime {get; set;}
+ public DateTime AccessTime { get; set; }
/// User comment for file
- public string Comment {get; set;}
+ public string Comment { get; set; }
/// True if UTF8 encoding for filename and comments, false if default (CP 437)
- public bool EncodeUTF8 {get; set;}
+ public bool EncodeUTF8 { get; set; }
/// Overriden method
/// Filename in Zip
@@ -63,14 +63,14 @@ public override string ToString()
}
}
-#region Public properties
+ #region Public properties
/// True if UTF8 encoding for filename and comments, false if default (CP 437)
- public bool EncodeUTF8 {get; set;} = false;
+ public bool EncodeUTF8 { get; set; } = false;
/// Force deflate algotithm even if it inflates the stored file. Off by default.
- public bool ForceDeflating {get; set;} = false;
-#endregion
+ public bool ForceDeflating { get; set; } = false;
+ #endregion
-#region Private fields
+ #region Private fields
// List of files to store
private List Files = new List();
// Filename of storage file
@@ -93,9 +93,9 @@ public override string ToString()
private static Encoding DefaultEncoding;
// leave the stream open after the ZipStorer object is disposed
private bool LeaveOpen;
-#endregion
+ #endregion
-#region Public methods
+ #region Public methods
static ZipStorer()
{
// Generate CRC32 table
@@ -267,6 +267,7 @@ public async Task AddStreamAsync(Compression method, string filena
Method = method,
EncodeUTF8 = this.EncodeUTF8,
FilenameInZip = NormalizedFilename(filenameInZip),
+ FileSize = !source.CanSeek || ForceDeflating ? 0 : source.Length,
Comment = comment ?? string.Empty,
Crc32 = 0, // to be updated later
HeaderOffset = this.ZipFileStream.Position, // offset within file of the start of this local record
@@ -604,9 +605,9 @@ public static bool RemoveEntries(ref ZipStorer zip, List zfes)
}
return true;
}
-#endregion
+ #endregion
-#region Private methods
+ #region Private methods
// Calculate the file offset by reading the corresponding local header
private long GetFileOffset(long _headerOffset)
{
@@ -904,9 +905,9 @@ private static uint DateTimeToDosTime(DateTime _dt)
private static byte[] CreateExtraInfo(ZipFileEntry _zfe, bool localHeader)
{
- var zip64FileSize = _zfe.FileSize >= 0xFFFFFFFF || localHeader && _zfe.CompressedSize >= 0xFFFFFFFF;
- var zip64CompSize = _zfe.CompressedSize >= 0xFFFFFFFF || localHeader && _zfe.FileSize >= 0xFFFFFFFF;
- var zip64Offset = _zfe.HeaderOffset >= 0xFFFFFFFF;
+ var zip64FileSize = _zfe.FileSize >= 0xFFFFFFFF || localHeader && _zfe.FileSize == 0;
+ var zip64CompSize = _zfe.CompressedSize >= 0xFFFFFFFF || localHeader && (_zfe.FileSize == 0 || _zfe.FileSize >= 0xFFFFFFFF);
+ var zip64Offset = !localHeader && _zfe.HeaderOffset >= 0xFFFFFFFF;
int offset = (zip64FileSize ? 8 : 0) + (zip64CompSize ? 8 : 0) + (zip64Offset ? 8 : 0);
if (offset != 0) offset += 4;
@@ -990,8 +991,6 @@ value is put in the data descriptor and in the central directory.
*/
private void UpdateCrcAndSizes(ZipFileEntry _zfe)
{
- var zip64Sizes = IsZip64ExtNeeded(_zfe, 1);
-
long lastPos = this.ZipFileStream.Position; // remember position
this.ZipFileStream.Position = _zfe.HeaderOffset + 4;
@@ -1000,17 +999,51 @@ private void UpdateCrcAndSizes(ZipFileEntry _zfe)
this.ZipFileStream.Position = _zfe.HeaderOffset + 8;
this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method
+ var zip64Sizes = UpdateLocalHeaderExtraFields(_zfe);
+
this.ZipFileStream.Position = _zfe.HeaderOffset + 14;
this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // Update CRC
this.ZipFileStream.Write(BitConverter.GetBytes(zip64Sizes ? 0xFFFFFFFF : _zfe.CompressedSize), 0, 4); // Compressed size
this.ZipFileStream.Write(BitConverter.GetBytes(zip64Sizes ? 0xFFFFFFFF : _zfe.FileSize), 0, 4); // Uncompressed size
- // and updating the extra fields?
-
this.ZipFileStream.Position = lastPos; // restore position
}
+ private bool UpdateLocalHeaderExtraFields(ZipFileEntry _zfe)
+ {
+ this.ZipFileStream.Position = _zfe.HeaderOffset + 26;
+
+ bool zip64Sizes = false;
+ var buffer = new byte[4];
+ this.ZipFileStream.Read(buffer, 0, 4);
+ var fileNameLength = BitConverter.ToUInt16(buffer, 0);
+ var extraFieldLength = BitConverter.ToUInt16(buffer, 2);
+ if (extraFieldLength > 0)
+ {
+ this.ZipFileStream.Seek(fileNameLength, SeekOrigin.Current);
+ var extraFieldsBuffer = new byte[extraFieldLength];
+ this.ZipFileStream.Read(extraFieldsBuffer, 0, extraFieldLength);
+
+ int pos = 0;
+ while (pos < extraFieldsBuffer.Length - 4)
+ {
+ uint extraId = BitConverter.ToUInt16(extraFieldsBuffer, pos);
+ uint length = BitConverter.ToUInt16(extraFieldsBuffer, pos + 2);
+
+ if (extraId == 0x0001) // ZIP64 Information
+ {
+ zip64Sizes = true;
+ this.ZipFileStream.Position = _zfe.HeaderOffset + 30 + fileNameLength + 4 + pos;
+ this.ZipFileStream.Write(BitConverter.GetBytes((ulong)_zfe.FileSize), 0, 8);
+ this.ZipFileStream.Write(BitConverter.GetBytes((ulong)_zfe.CompressedSize), 0, 8);
+ }
+ pos += (int)length + 4;
+ }
+ }
+ return zip64Sizes;
+ }
+
// Replaces backslashes with slashes to store in zip header
private static string NormalizedFilename(string filename)
{
@@ -1107,9 +1140,9 @@ private bool ReadFileInfo()
return false;
}
-#endregion
+ #endregion
-#region IDisposable implementation
+ #region IDisposable implementation
///
/// Closes the Zip file stream
///
@@ -1130,6 +1163,6 @@ protected virtual void Dispose(bool disposing)
IsDisposed = true;
}
}
-#endregion
+ #endregion
}
}
diff --git a/test/Program.cs b/test/Program.cs
index d5d9166..1c1e78d 100644
--- a/test/Program.cs
+++ b/test/Program.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.IO.Compression;
+using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -18,6 +19,7 @@ static void Main()
const string sampleFile1 = "sample1.zip";
const string sampleFile3 = "sample3.zip";
+ const string sampleFile5 = "sample5.zip";
const string sampleFile = "sample.zip";
private static byte[] buffer;
@@ -171,6 +173,85 @@ public void Compression_Test()
}
}
+ [TestMethod]
+ public void Zip64_Test()
+ {
+ var dir = Path.Combine(Environment.CurrentDirectory, "SampleFiles5");
+ if (new DriveInfo(dir).AvailableFreeSpace < ((long)16496 * 1024 * 1024)) throw new Exception("Not enough disk space (16.1 GB) for test!");
+ if (Directory.Exists(dir)) Directory.Delete(dir, true);
+ if (Directory.Exists(dir + "_2")) Directory.Delete(dir + "_2", true);
+ Directory.CreateDirectory(dir);
+ Directory.CreateDirectory(dir + "_2");
+ File.Delete(Path.Combine(dir, "..", sampleFile5));
+
+ // generate three test files
+ // standard text
+ File.WriteAllBytes(Path.Combine(dir, "File1.txt"), buffer);
+ var txtBuffer = new byte[65538];
+ using (var mem = new MemoryStream(txtBuffer))
+ using (var bw = new BinaryWriter(mem, Encoding.ASCII))
+ {
+ for (int i = 0; i < 5958; i++)
+ {
+ bw.Write(Encoding.ASCII.GetBytes("1234567890\n"));
+ }
+ }
+ // one larger than 0xFFFFFFFF and one with 0xFFFFFFFE bytes
+ for (int n = 2; n <= 3; n++)
+ {
+ using (var fs = new FileStream(Path.Combine(dir, $"File{n}.txt"), FileMode.Create))
+ {
+ for (var i = 0; i < (n == 2 ? 66000 : 65534); i++)
+ {
+ fs.Write(txtBuffer, 0, txtBuffer.Length);
+ }
+ if (n == 3) fs.Write(txtBuffer, 0, 2);
+ }
+ }
+
+ // zip them
+ using (ZipStorer zip = ZipStorer.Create(Path.Combine(dir, "..", sampleFile5)))
+ {
+ zip.AddFile(ZipStorer.Compression.Deflate, Path.Combine(dir, "File1.txt"), "File1.txt"); // normal file
+ zip.AddFile(ZipStorer.Compression.Deflate, Path.Combine(dir, "File2.txt"), "File2.txt"); // Zip64 file size, normal compressed size and offset
+ zip.AddFile(ZipStorer.Compression.Store, Path.Combine(dir, "File3.txt"), "File3.txt"); // normal file size and offset
+ zip.AddFile(ZipStorer.Compression.Deflate, Path.Combine(dir, "File2.txt"), "File4.txt"); // Zip64 file size and offset
+ }
+
+ // unzip and compare them
+ using (ZipStorer zip = ZipStorer.Open(Path.Combine(dir, "..", sampleFile5), FileAccess.Read))
+ {
+ var entries = zip.ReadCentralDir();
+ for (var n = 0; n < 4; n++)
+ {
+ zip.ExtractFile(entries[n], Path.Combine(dir + "_2", entries[n].FilenameInZip));
+ using (var fs1 = new FileStream(Path.Combine(dir, entries[n == 3 ? 1 : n].FilenameInZip), FileMode.Open))
+ using (var fs2 = new FileStream(Path.Combine(dir + "_2", entries[n].FilenameInZip), FileMode.Open))
+ {
+ Assert.IsTrue(StreamsAreEqual(fs1, fs2));
+ }
+ File.Delete(Path.Combine(dir + "_2", entries[n].FilenameInZip));
+ }
+ }
+ }
+
+ private bool StreamsAreEqual(Stream s1, Stream s2)
+ {
+ if (s1.Length != s2.Length) return false;
+
+ var bytes1 = new byte[65536];
+ var bytes2 = new byte[65536];
+ long bytesLeft = s1.Length;
+ while (bytesLeft > 0)
+ {
+ var bytesRead1 = s1.Read(bytes1, 0, bytes1.Length);
+ var bytesRead2 = s2.Read(bytes2, 0, bytes2.Length);
+ if (!bytes1.SequenceEqual(bytes2)) return false;
+ bytesLeft -= bytesRead1;
+ }
+ return true;
+ }
+
public void createSampleFile()
{
using (var mem = new MemoryStream(buffer))