Merge pull request #803 from DannyBoyk/802_Add_UnixTimeExtraField_Support_Zips

Add support for the UnixTimeExtraField in Zip files
This commit is contained in:
Adam Hathcock
2024-01-15 08:19:29 +00:00
committed by GitHub
3 changed files with 142 additions and 2 deletions

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -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) { }
/// <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) =>
@@ -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)
};
}