Skip to content

Commit 861a313

Browse files
Merge PR #463: Improve support for Unix timestamps in ZIP archives
* 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.
1 parent 97a317a commit 861a313

File tree

2 files changed

+51
-61
lines changed

2 files changed

+51
-61
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public ZipEntry(ZipEntry entry)
238238
size = entry.size;
239239
compressedSize = entry.compressedSize;
240240
crc = entry.crc;
241-
dosTime = entry.dosTime;
241+
dateTime = entry.DateTime;
242242
method = entry.method;
243243
comment = entry.comment;
244244
versionToExtract = entry.versionToExtract;
@@ -696,18 +696,54 @@ public long DosTime
696696
}
697697
else
698698
{
699-
return dosTime;
699+
var year = (uint)DateTime.Year;
700+
var month = (uint)DateTime.Month;
701+
var day = (uint)DateTime.Day;
702+
var hour = (uint)DateTime.Hour;
703+
var minute = (uint)DateTime.Minute;
704+
var second = (uint)DateTime.Second;
705+
706+
if (year < 1980)
707+
{
708+
year = 1980;
709+
month = 1;
710+
day = 1;
711+
hour = 0;
712+
minute = 0;
713+
second = 0;
714+
}
715+
else if (year > 2107)
716+
{
717+
year = 2107;
718+
month = 12;
719+
day = 31;
720+
hour = 23;
721+
minute = 59;
722+
second = 59;
723+
}
724+
725+
return ((year - 1980) & 0x7f) << 25 |
726+
(month << 21) |
727+
(day << 16) |
728+
(hour << 11) |
729+
(minute << 5) |
730+
(second >> 1);
700731
}
701732
}
702733

703734
set
704735
{
705736
unchecked
706737
{
707-
dosTime = (uint)value;
738+
var dosTime = (uint)value;
739+
uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
740+
uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
741+
uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
742+
uint mon = Math.Max(1, Math.Min(12, ((uint)(value >> 21) & 0xf)));
743+
uint year = ((dosTime >> 25) & 0x7f) + 1980;
744+
int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f)));
745+
DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc);
708746
}
709-
710-
known |= Known.Time;
711747
}
712748
}
713749

@@ -721,49 +757,13 @@ public DateTime DateTime
721757
{
722758
get
723759
{
724-
uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
725-
uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
726-
uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
727-
uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
728-
uint year = ((dosTime >> 25) & 0x7f) + 1980;
729-
int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
730-
return new System.DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
760+
return dateTime;
731761
}
732762

733763
set
734764
{
735-
var year = (uint)value.Year;
736-
var month = (uint)value.Month;
737-
var day = (uint)value.Day;
738-
var hour = (uint)value.Hour;
739-
var minute = (uint)value.Minute;
740-
var second = (uint)value.Second;
741-
742-
if (year < 1980)
743-
{
744-
year = 1980;
745-
month = 1;
746-
day = 1;
747-
hour = 0;
748-
minute = 0;
749-
second = 0;
750-
}
751-
else if (year > 2107)
752-
{
753-
year = 2107;
754-
month = 12;
755-
day = 31;
756-
hour = 23;
757-
minute = 59;
758-
second = 59;
759-
}
760-
761-
DosTime = ((year - 1980) & 0x7f) << 25 |
762-
(month << 21) |
763-
(day << 16) |
764-
(hour << 11) |
765-
(minute << 5) |
766-
(second >> 1);
765+
dateTime = value;
766+
known |= Known.Time;
767767
}
768768
}
769769

@@ -1088,14 +1088,14 @@ internal void ProcessExtraData(bool localHeader)
10881088
}
10891089
}
10901090

1091-
DateTime = GetDateTime(extraData);
1091+
DateTime = GetDateTime(extraData) ?? DateTime;
10921092
if (method == CompressionMethod.WinZipAES)
10931093
{
10941094
ProcessAESExtraData(extraData);
10951095
}
10961096
}
10971097

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

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

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

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

13331321
private CompressionMethod method = CompressionMethod.Deflated;
13341322
private byte[] extra;

test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,12 @@ public void DateAndTime()
195195

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

200201
// Under the limit are set to min.
201202
ze.DateTime = new DateTime(1906, 12, 4);
203+
ze.DosTime = ze.DosTime;
202204
Assert.AreEqual(new DateTime(1980, 1, 1, 0, 0, 0), ze.DateTime);
203205
}
204206

0 commit comments

Comments
 (0)