diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
index fc2cb262..184bdfa3 100644
--- a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
@@ -85,6 +85,36 @@ internal class DirectoryEntryHeader : ZipFileEntry
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; }
diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
index 93cc55ac..d0a87100 100644
--- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
@@ -64,6 +64,36 @@ internal class LocalEntryHeader : ZipFileEntry
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; }
diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs
index 80ec9b8f..88e4a187 100644
--- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs
+++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Buffers.Binary;
using System.Text;
@@ -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
@@ -145,6 +146,84 @@ internal sealed class Zip64ExtendedInformationExtraField : ExtraData
public uint VolumeNumber { get; private set; }
}
+internal sealed class UnixTimeExtraField : ExtraData
+{
+ public UnixTimeExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
+ : base(type, length, dataBytes) { }
+
+ ///
+ /// The unix modified time, last access time, and creation time, if set.
+ ///
+ /// Must return Tuple explicitly due to net462 support.
+ internal Tuple 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(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) =>
@@ -154,6 +233,7 @@ internal static class LocalEntryHeaderExtraFactory
=> new ExtraUnicodePathExtraField(type, length, extraData),
ExtraDataType.Zip64ExtendedInformationExtraField
=> new Zip64ExtendedInformationExtraField(type, length, extraData),
+ ExtraDataType.UnixTimeExtraField => new UnixTimeExtraField(type, length, extraData),
_ => new ExtraData(type, length, extraData)
};
}