Skip to content

Commit

Permalink
Add support for the UnixTimeExtraField in Zip files
Browse files Browse the repository at this point in the history
Fixes #802
  • Loading branch information
DannyBoyk committed Jan 12, 2024
1 parent 396717e commit badc2c0
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 2 deletions.
32 changes: 32 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,38 @@ 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
32 changes: 32 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,38 @@ 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,74 @@ 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 +223,8 @@ 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 badc2c0

Please sign in to comment.