Skip to content

Commit

Permalink
Merge PR #463: Improve support for Unix timestamps in ZIP archives
Browse files Browse the repository at this point in the history
* Store ZipEntry.DateTime in dedicated backing field  
This allows reading values with a higher resolution than DOS time (2 second accuracy).

* Fix getting unix modification time in ZIP files
InfoZIP actually does respect a file's modification time, even if the access time and/or creation time are not set.

* Remove backing field for ZipEntry.DosTime, convert to and from ZipEntry.DateTime instead
This prevents the two values from becoming potentially inconsistent.
  • Loading branch information
bastianeicher authored Jun 19, 2020
1 parent 97a317a commit 861a313
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 61 deletions.
110 changes: 49 additions & 61 deletions src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public ZipEntry(ZipEntry entry)
size = entry.size;
compressedSize = entry.compressedSize;
crc = entry.crc;
dosTime = entry.dosTime;
dateTime = entry.DateTime;
method = entry.method;
comment = entry.comment;
versionToExtract = entry.versionToExtract;
Expand Down Expand Up @@ -696,18 +696,54 @@ public long DosTime
}
else
{
return dosTime;
var year = (uint)DateTime.Year;
var month = (uint)DateTime.Month;
var day = (uint)DateTime.Day;
var hour = (uint)DateTime.Hour;
var minute = (uint)DateTime.Minute;
var second = (uint)DateTime.Second;

if (year < 1980)
{
year = 1980;
month = 1;
day = 1;
hour = 0;
minute = 0;
second = 0;
}
else if (year > 2107)
{
year = 2107;
month = 12;
day = 31;
hour = 23;
minute = 59;
second = 59;
}

return ((year - 1980) & 0x7f) << 25 |
(month << 21) |
(day << 16) |
(hour << 11) |
(minute << 5) |
(second >> 1);
}
}

set
{
unchecked
{
dosTime = (uint)value;
var dosTime = (uint)value;
uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
uint mon = Math.Max(1, Math.Min(12, ((uint)(value >> 21) & 0xf)));
uint year = ((dosTime >> 25) & 0x7f) + 1980;
int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f)));
DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc);
}

known |= Known.Time;
}
}

Expand All @@ -721,49 +757,13 @@ public DateTime DateTime
{
get
{
uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
uint year = ((dosTime >> 25) & 0x7f) + 1980;
int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
return new System.DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
return dateTime;
}

set
{
var year = (uint)value.Year;
var month = (uint)value.Month;
var day = (uint)value.Day;
var hour = (uint)value.Hour;
var minute = (uint)value.Minute;
var second = (uint)value.Second;

if (year < 1980)
{
year = 1980;
month = 1;
day = 1;
hour = 0;
minute = 0;
second = 0;
}
else if (year > 2107)
{
year = 2107;
month = 12;
day = 31;
hour = 23;
minute = 59;
second = 59;
}

DosTime = ((year - 1980) & 0x7f) << 25 |
(month << 21) |
(day << 16) |
(hour << 11) |
(minute << 5) |
(second >> 1);
dateTime = value;
known |= Known.Time;
}
}

Expand Down Expand Up @@ -1088,14 +1088,14 @@ internal void ProcessExtraData(bool localHeader)
}
}

DateTime = GetDateTime(extraData);
DateTime = GetDateTime(extraData) ?? DateTime;
if (method == CompressionMethod.WinZipAES)
{
ProcessAESExtraData(extraData);
}
}

private DateTime GetDateTime(ZipExtraData extraData)
private DateTime? GetDateTime(ZipExtraData extraData)
{
// Check for NT timestamp
// NOTE: Disable by default to match behavior of InfoZIP
Expand All @@ -1107,22 +1107,10 @@ private DateTime GetDateTime(ZipExtraData extraData)

// Check for Unix timestamp
ExtendedUnixData unixData = extraData.GetData<ExtendedUnixData>();
if (unixData != null &&
// Only apply modification time, but require all other values to be present
// This is done to match InfoZIP's behaviour
((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) &&
((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) &&
((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0))
if (unixData != null && unixData.Include.HasFlag(ExtendedUnixData.Flags.ModificationTime))
return unixData.ModificationTime;

// Fall back to DOS time
uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
uint year = ((dosTime >> 25) & 0x7f) + 1980;
int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc);
return null;
}

// For AES the method in the entry is 99, and the real compression method is in the extradata
Expand Down Expand Up @@ -1328,7 +1316,7 @@ public static string CleanName(string name)
private ulong compressedSize;
private ushort versionToExtract; // Version required to extract (library handles <= 2.0)
private uint crc;
private uint dosTime;
private DateTime dateTime;

private CompressionMethod method = CompressionMethod.Deflated;
private byte[] extra;
Expand Down
2 changes: 2 additions & 0 deletions test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,12 @@ public void DateAndTime()

// Over the limit are set to max.
ze.DateTime = new DateTime(2108, 1, 1);
ze.DosTime = ze.DosTime;
Assert.AreEqual(new DateTime(2107, 12, 31, 23, 59, 58), ze.DateTime);

// Under the limit are set to min.
ze.DateTime = new DateTime(1906, 12, 4);
ze.DosTime = ze.DosTime;
Assert.AreEqual(new DateTime(1980, 1, 1, 0, 0, 0), ze.DateTime);
}

Expand Down

0 comments on commit 861a313

Please sign in to comment.