Skip to content

Commit 765eb69

Browse files
authored
PR #583: Restore entry times on FastZip extract
* FastZip - fix TimeSetting control when extracting Zip entries * Add tests for FastZip file times Co-authored-by: va4es2 <[email protected]> ref #555 #566
1 parent c959373 commit 765eb69

File tree

4 files changed

+279
-3
lines changed

4 files changed

+279
-3
lines changed

src/ICSharpCode.SharpZipLib/Zip/FastZip.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.IO;
55
using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
6+
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;
67

78
namespace ICSharpCode.SharpZipLib.Zip
89
{
@@ -195,6 +196,29 @@ public FastZip()
195196
{
196197
}
197198

199+
/// <summary>
200+
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="TimeSetting"/>
201+
/// </summary>
202+
/// <param name="timeSetting">The <see cref="TimeSetting">time setting</see> to use when creating or extracting <see cref="ZipEntry">Zip entries</see>.</param>
203+
/// <remarks>Using <see cref="TimeSetting.LastAccessTime">TimeSetting.LastAccessTime</see><see cref="TimeSetting.LastAccessTimeUtc">[Utc]</see> when
204+
/// creating an archive will set the file time to the moment of reading.
205+
/// </remarks>
206+
public FastZip(TimeSetting timeSetting)
207+
{
208+
entryFactory_ = new ZipEntryFactory(timeSetting);
209+
restoreDateTimeOnExtract_ = true;
210+
}
211+
212+
/// <summary>
213+
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="DateTime"/>
214+
/// </summary>
215+
/// <param name="time">The time to set all <see cref="ZipEntry.DateTime"/> values for created or extracted <see cref="ZipEntry">Zip Entries</see>.</param>
216+
public FastZip(DateTime time)
217+
{
218+
entryFactory_ = new ZipEntryFactory(time);
219+
restoreDateTimeOnExtract_ = true;
220+
}
221+
198222
/// <summary>
199223
/// Initialise a new instance of <see cref="FastZip"/>
200224
/// </summary>
@@ -735,7 +759,39 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)
735759

736760
if (restoreDateTimeOnExtract_)
737761
{
738-
File.SetLastWriteTime(targetName, entry.DateTime);
762+
switch (entryFactory_.Setting)
763+
{
764+
case TimeSetting.CreateTime:
765+
File.SetCreationTime(targetName, entry.DateTime);
766+
break;
767+
768+
case TimeSetting.CreateTimeUtc:
769+
File.SetCreationTimeUtc(targetName, entry.DateTime);
770+
break;
771+
772+
case TimeSetting.LastAccessTime:
773+
File.SetLastAccessTime(targetName, entry.DateTime);
774+
break;
775+
776+
case TimeSetting.LastAccessTimeUtc:
777+
File.SetLastAccessTimeUtc(targetName, entry.DateTime);
778+
break;
779+
780+
case TimeSetting.LastWriteTime:
781+
File.SetLastWriteTime(targetName, entry.DateTime);
782+
break;
783+
784+
case TimeSetting.LastWriteTimeUtc:
785+
File.SetLastWriteTimeUtc(targetName, entry.DateTime);
786+
break;
787+
788+
case TimeSetting.Fixed:
789+
File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime);
790+
break;
791+
792+
default:
793+
throw new ZipException("Unhandled time setting in ExtractFileEntry");
794+
}
739795
}
740796

741797
if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1))
@@ -809,7 +865,39 @@ private void ExtractEntry(ZipEntry entry)
809865
Directory.CreateDirectory(dirName);
810866
if (entry.IsDirectory && restoreDateTimeOnExtract_)
811867
{
812-
Directory.SetLastWriteTime(dirName, entry.DateTime);
868+
switch (entryFactory_.Setting)
869+
{
870+
case TimeSetting.CreateTime:
871+
Directory.SetCreationTime(dirName, entry.DateTime);
872+
break;
873+
874+
case TimeSetting.CreateTimeUtc:
875+
Directory.SetCreationTimeUtc(dirName, entry.DateTime);
876+
break;
877+
878+
case TimeSetting.LastAccessTime:
879+
Directory.SetLastAccessTime(dirName, entry.DateTime);
880+
break;
881+
882+
case TimeSetting.LastAccessTimeUtc:
883+
Directory.SetLastAccessTimeUtc(dirName, entry.DateTime);
884+
break;
885+
886+
case TimeSetting.LastWriteTime:
887+
Directory.SetLastWriteTime(dirName, entry.DateTime);
888+
break;
889+
890+
case TimeSetting.LastWriteTimeUtc:
891+
Directory.SetLastWriteTimeUtc(dirName, entry.DateTime);
892+
break;
893+
894+
case TimeSetting.Fixed:
895+
Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime);
896+
break;
897+
898+
default:
899+
throw new ZipException("Unhandled time setting in ExtractEntry");
900+
}
813901
}
814902
}
815903
else

src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using ICSharpCode.SharpZipLib.Core;
3+
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;
24

35
namespace ICSharpCode.SharpZipLib.Zip
46
{
@@ -50,5 +52,16 @@ public interface IEntryFactory
5052
/// Get/set the <see cref="INameTransform"></see> applicable.
5153
/// </summary>
5254
INameTransform NameTransform { get; set; }
55+
56+
/// <summary>
57+
/// Get the <see cref="TimeSetting"/> in use.
58+
/// </summary>
59+
TimeSetting Setting { get; }
60+
61+
/// <summary>
62+
/// Get the <see cref="DateTime"/> value to use when <see cref="Setting"/> is set to <see cref="TimeSetting.Fixed"/>,
63+
/// or if not specified, the value of <see cref="DateTime.Now"/> when the class was the initialized
64+
/// </summary>
65+
DateTime FixedDateTime { get; }
5366
}
5467
}

src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem)
364364

365365
private INameTransform nameTransform_;
366366
private DateTime fixedDateTime_ = DateTime.Now;
367-
private TimeSetting timeSetting_;
367+
private TimeSetting timeSetting_ = TimeSetting.LastWriteTime;
368368
private bool isUnicodeText_;
369369

370370
private int getAttributes_ = -1;

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

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Linq;
88
using System.Text;
9+
using TimeSetting = ICSharpCode.SharpZipLib.Zip.ZipEntryFactory.TimeSetting;
910

1011
namespace ICSharpCode.SharpZipLib.Tests.Zip
1112
{
@@ -685,5 +686,179 @@ public void CreateZipShouldLeaveOutputStreamOpenIfRequested(bool leaveOpen)
685686
}
686687
}
687688
}
689+
690+
[Category("Zip")]
691+
[Category("CreatesTempFile")]
692+
[Test]
693+
public void CreateZipShouldSetTimeOnEntriesFromConstructorDateTime()
694+
{
695+
var targetTime = TestTargetTime(TimeSetting.Fixed);
696+
var fastZip = new FastZip(targetTime);
697+
var target = CreateFastZipTestArchiveWithAnEntry(fastZip);
698+
var archive = new MemoryStream(target.ToArray());
699+
using (var zf = new ZipFile(archive))
700+
{
701+
Assert.AreEqual(targetTime, zf[0].DateTime);
702+
}
703+
}
704+
705+
[Category("Zip")]
706+
[Category("CreatesTempFile")]
707+
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
708+
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
709+
public void CreateZipShouldSetTimeOnEntriesFromConstructorTimeSetting(TimeSetting timeSetting)
710+
{
711+
var targetTime = TestTargetTime(timeSetting);
712+
var fastZip = new FastZip(timeSetting);
713+
714+
var alterTime = (Action<FileInfo>) null;
715+
switch(timeSetting)
716+
{
717+
case TimeSetting.LastWriteTime: alterTime = fi => fi.LastWriteTime = targetTime; break;
718+
case TimeSetting.LastWriteTimeUtc: alterTime = fi => fi.LastWriteTimeUtc = targetTime; break;
719+
case TimeSetting.CreateTime: alterTime = fi => fi.CreationTime = targetTime; break;
720+
case TimeSetting.CreateTimeUtc: alterTime = fi => fi.CreationTimeUtc = targetTime; break;
721+
}
722+
723+
var target = CreateFastZipTestArchiveWithAnEntry(fastZip, alterTime);
724+
// Check that the file contents are correct in both cases
725+
var archive = new MemoryStream(target.ToArray());
726+
using (var zf = new ZipFile(archive))
727+
{
728+
Assert.AreEqual(TestTargetTime(timeSetting), zf[0].DateTime);
729+
}
730+
}
731+
732+
[Category("Zip")]
733+
[Category("CreatesTempFile")]
734+
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
735+
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
736+
[TestCase(TimeSetting.Fixed)]
737+
public void ExtractZipShouldSetTimeOnFilesFromConstructorTimeSetting(TimeSetting timeSetting)
738+
{
739+
var targetTime = ExpectedFixedTime();
740+
var archiveStream = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));
741+
742+
if (timeSetting == TimeSetting.Fixed)
743+
{
744+
Assert.Ignore("Fixed time without specifying a time is undefined");
745+
}
746+
747+
var fastZip = new FastZip(timeSetting);
748+
using (var extractDir = new Utils.TempDir())
749+
{
750+
fastZip.ExtractZip(archiveStream, extractDir.Fullpath, FastZip.Overwrite.Always,
751+
_ => true, "", "", true, true, false);
752+
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
753+
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, timeSetting));
754+
}
755+
}
756+
757+
[Category("Zip")]
758+
[Category("CreatesTempFile")]
759+
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
760+
public void ExtractZipShouldSetTimeOnFilesFromConstructorDateTime(DateTimeKind dtk)
761+
{
762+
// Create the archive with a fixed "bad" datetime
763+
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(UnexpectedFixedTime(dtk)));
764+
765+
// Extract the archive with a fixed time override
766+
var targetTime = ExpectedFixedTime(dtk);
767+
var fastZip = new FastZip(targetTime);
768+
using (var extractDir = new Utils.TempDir())
769+
{
770+
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
771+
_ => true, "", "", true, true, false);
772+
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
773+
var fileTime = FileTimeFromTimeSetting(fi, TimeSetting.Fixed);
774+
if (fileTime.Kind != dtk) fileTime = fileTime.ToUniversalTime();
775+
Assert.AreEqual(targetTime, fileTime);
776+
}
777+
}
778+
779+
[Category("Zip")]
780+
[Category("CreatesTempFile")]
781+
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
782+
public void ExtractZipShouldSetTimeOnFilesWithEmptyConstructor(DateTimeKind dtk)
783+
{
784+
// Create the archive with a fixed datetime
785+
var targetTime = ExpectedFixedTime(dtk);
786+
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));
787+
788+
// Extract the archive with an empty constructor
789+
var fastZip = new FastZip();
790+
using (var extractDir = new Utils.TempDir())
791+
{
792+
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
793+
_ => true, "", "", true, true, false);
794+
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
795+
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, TimeSetting.Fixed));
796+
}
797+
}
798+
799+
private static bool IsLastAccessTime(TimeSetting ts)
800+
=> ts == TimeSetting.LastAccessTime || ts == TimeSetting.LastAccessTimeUtc;
801+
802+
private static DateTime FileTimeFromTimeSetting(FileInfo fi, TimeSetting timeSetting)
803+
{
804+
switch (timeSetting)
805+
{
806+
case TimeSetting.LastWriteTime: return fi.LastWriteTime;
807+
case TimeSetting.LastWriteTimeUtc: return fi.LastWriteTimeUtc;
808+
case TimeSetting.CreateTime: return fi.CreationTime;
809+
case TimeSetting.CreateTimeUtc: return fi.CreationTimeUtc;
810+
case TimeSetting.LastAccessTime: return fi.LastAccessTime;
811+
case TimeSetting.LastAccessTimeUtc: return fi.LastAccessTimeUtc;
812+
case TimeSetting.Fixed: return fi.LastWriteTime;
813+
}
814+
815+
throw new ArgumentException("Invalid TimeSetting", nameof(timeSetting));
816+
}
817+
818+
private static DateTime TestTargetTime(TimeSetting ts)
819+
{
820+
var dtk = ts == TimeSetting.CreateTimeUtc
821+
|| ts == TimeSetting.LastWriteTimeUtc
822+
|| ts == TimeSetting.LastAccessTimeUtc
823+
? DateTimeKind.Utc
824+
: DateTimeKind.Local;
825+
826+
return IsLastAccessTime(ts)
827+
// AccessTime will be altered by reading/writing the file entry
828+
? CurrentTime(dtk)
829+
: ExpectedFixedTime(dtk);
830+
}
831+
832+
private static DateTime CurrentTime(DateTimeKind kind)
833+
{
834+
var now = kind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now;
835+
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, (now.Second / 2) * 2, kind);
836+
}
837+
838+
private static DateTime ExpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
839+
=> new DateTime(2010, 5, 30, 16, 22, 50, dtk);
840+
private static DateTime UnexpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
841+
=> new DateTime(1980, 10, 11, 22, 39, 30, dtk);
842+
843+
private const string SingleEntryFileName = "testEntry.dat";
844+
845+
private static TrackedMemoryStream CreateFastZipTestArchiveWithAnEntry(FastZip fastZip, Action<FileInfo> alterFile = null)
846+
{
847+
var target = new TrackedMemoryStream();
848+
849+
using (var tempFolder = new Utils.TempDir())
850+
{
851+
852+
// Create test input file
853+
var addFile = Path.Combine(tempFolder.Fullpath, SingleEntryFileName);
854+
MakeTempFile(addFile, 16);
855+
var fi = new FileInfo(addFile);
856+
alterFile?.Invoke(fi);
857+
858+
fastZip.CreateZip(target, tempFolder.Fullpath, false, SingleEntryFileName, null, leaveOpen: true);
859+
}
860+
861+
return target;
862+
}
688863
}
689864
}

0 commit comments

Comments
 (0)