Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Zip64 implementation (2) #64

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix Zip64 implementation (2)
  • Loading branch information
thomas694 committed Jan 21, 2025
commit e3ef1546f9af58bc2e9fdf47fe0e89e9b88fc08e
97 changes: 65 additions & 32 deletions src/ZipStorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,31 @@ public enum Compression : ushort
public class ZipFileEntry
{
/// <summary>Compression method</summary>
public Compression Method {get; set;}
public Compression Method { get; set; }
/// <summary>Full path and filename as stored in Zip</summary>
public string FilenameInZip {get; set;}
public string FilenameInZip { get; set; }
/// <summary>Original file size</summary>
public long FileSize {get; set;}
public long FileSize { get; set; }
/// <summary>Compressed file size</summary>
public long CompressedSize {get; set;}
public long CompressedSize { get; set; }
/// <summary>Offset of header information inside Zip storage</summary>
public long HeaderOffset {get; set;}
public long HeaderOffset { get; set; }
/// <summary>Offset of file inside Zip storage</summary>
public long FileOffset {get; set;}
public long FileOffset { get; set; }
/// <summary>Size of header information</summary>
public uint HeaderSize {get; set;}
public uint HeaderSize { get; set; }
/// <summary>32-bit checksum of entire file</summary>
public uint Crc32 {get; set;}
public uint Crc32 { get; set; }
/// <summary>Last modification time of file</summary>
public DateTime ModifyTime {get; set;}
public DateTime ModifyTime { get; set; }
/// <summary>Creation time of file</summary>
public DateTime CreationTime {get; set;}
public DateTime CreationTime { get; set; }
/// <summary>Last access time of file</summary>
public DateTime AccessTime {get; set;}
public DateTime AccessTime { get; set; }
/// <summary>User comment for file</summary>
public string Comment {get; set;}
public string Comment { get; set; }
/// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
public bool EncodeUTF8 {get; set;}
public bool EncodeUTF8 { get; set; }

/// <summary>Overriden method</summary>
/// <returns>Filename in Zip</returns>
Expand All @@ -63,14 +63,14 @@ public override string ToString()
}
}

#region Public properties
#region Public properties
/// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
public bool EncodeUTF8 {get; set;} = false;
public bool EncodeUTF8 { get; set; } = false;
/// <summary>Force deflate algotithm even if it inflates the stored file. Off by default.</summary>
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<ZipFileEntry> Files = new List<ZipFileEntry>();
// Filename of storage file
Expand All @@ -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
Expand Down Expand Up @@ -267,6 +267,7 @@ public async Task<ZipFileEntry> 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
Expand Down Expand Up @@ -604,9 +605,9 @@ public static bool RemoveEntries(ref ZipStorer zip, List<ZipFileEntry> 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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down Expand Up @@ -1107,9 +1140,9 @@ private bool ReadFileInfo()

return false;
}
#endregion
#endregion

#region IDisposable implementation
#region IDisposable implementation
/// <summary>
/// Closes the Zip file stream
/// </summary>
Expand All @@ -1130,6 +1163,6 @@ protected virtual void Dispose(bool disposing)
IsDisposed = true;
}
}
#endregion
#endregion
}
}