Skip to content

Commit

Permalink
Merge pull request #803 from DannyBoyk/802_Add_UnixTimeExtraField_Sup…
Browse files Browse the repository at this point in the history
…port_Zips

Add support for the UnixTimeExtraField in Zip files
  • Loading branch information
adamhathcock authored Jan 15, 2024
2 parents 396717e + d69559e commit ed57cfd
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 2 deletions.
30 changes: 30 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ internal override void Read(BinaryReader reader)
RelativeOffsetOfEntryHeader = zip64ExtraData.RelativeOffsetOfEntryHeader;
}
}

var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);

if (unixTimeExtra is not null)
{
// Tuple order is last modified time, last access time, and creation time.
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;

if (unixTimeTuple.Item1.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
else if (unixTimeTuple.Item2.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
else if (unixTimeTuple.Item3.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
}
}

internal ushort Version { get; private set; }
Expand Down
30 changes: 30 additions & 0 deletions src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ internal override void Read(BinaryReader reader)
UncompressedSize = zip64ExtraData.UncompressedSize;
}
}

var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);

if (unixTimeExtra is not null)
{
// Tuple order is last modified time, last access time, and creation time.
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;

if (unixTimeTuple.Item1.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
else if (unixTimeTuple.Item2.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
else if (unixTimeTuple.Item3.HasValue)
{
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);

LastModifiedDate = (ushort)(dosTime >> 16);
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
}
}
}

internal ushort Version { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers.Binary;
using System.Text;

Expand All @@ -13,7 +13,8 @@ internal enum ExtraDataType : ushort
// Third Party Mappings
// -Info-ZIP Unicode Path Extra Field
UnicodePathExtraField = 0x7075,
Zip64ExtendedInformationExtraField = 0x0001
Zip64ExtendedInformationExtraField = 0x0001,
UnixTimeExtraField = 0x5455
}

internal class ExtraData
Expand Down Expand Up @@ -145,6 +146,84 @@ ushort diskNumber
public uint VolumeNumber { get; private set; }
}

internal sealed class UnixTimeExtraField : ExtraData
{
public UnixTimeExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
: base(type, length, dataBytes) { }

/// <summary>
/// The unix modified time, last access time, and creation time, if set.
/// </summary>
/// <remarks>Must return Tuple explicitly due to net462 support.</remarks>
internal Tuple<DateTime?, DateTime?, DateTime?> UnicodeTimes
{
get
{
// There has to be at least 5 byte for there to be a timestamp.
// 1 byte for flags and 4 bytes for a timestamp.
if (DataBytes is null || DataBytes.Length < 5)
{
return Tuple.Create<DateTime?, DateTime?, DateTime?>(null, null, null);
}

var flags = DataBytes[0];
var isModifiedTimeSpecified = (flags & 0x01) == 1;
var isLastAccessTimeSpecified = (flags & 0x02) == 1;
var isCreationTimeSpecified = (flags & 0x04) == 1;
var currentIndex = 1;
DateTime? modifiedTime = null;
DateTime? lastAccessTime = null;
DateTime? creationTime = null;

if (isModifiedTimeSpecified)
{
var modifiedEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
DataBytes.AsSpan(currentIndex, 4)
);

currentIndex += 4;
modifiedTime = DateTimeOffset.FromUnixTimeSeconds(modifiedEpochTime).UtcDateTime;
}

if (isLastAccessTimeSpecified)
{
if (currentIndex + 4 > DataBytes.Length)
{
throw new ArchiveException("Invalid UnicodeExtraTime field");
}

var lastAccessEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
DataBytes.AsSpan(currentIndex, 4)
);

currentIndex += 4;
lastAccessTime = DateTimeOffset
.FromUnixTimeSeconds(lastAccessEpochTime)
.UtcDateTime;
}

if (isCreationTimeSpecified)
{
if (currentIndex + 4 > DataBytes.Length)
{
throw new ArchiveException("Invalid UnicodeExtraTime field");
}

var creationTimeEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
DataBytes.AsSpan(currentIndex, 4)
);

currentIndex += 4;
creationTime = DateTimeOffset
.FromUnixTimeSeconds(creationTimeEpochTime)
.UtcDateTime;
}

return Tuple.Create(modifiedTime, lastAccessTime, creationTime);
}
}
}

internal static class LocalEntryHeaderExtraFactory
{
internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extraData) =>
Expand All @@ -154,6 +233,7 @@ internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extra
=> new ExtraUnicodePathExtraField(type, length, extraData),
ExtraDataType.Zip64ExtendedInformationExtraField
=> new Zip64ExtendedInformationExtraField(type, length, extraData),
ExtraDataType.UnixTimeExtraField => new UnixTimeExtraField(type, length, extraData),
_ => new ExtraData(type, length, extraData)
};
}

0 comments on commit ed57cfd

Please sign in to comment.