Skip to content

Commit

Permalink
PR #583: Restore entry times on FastZip extract
Browse files Browse the repository at this point in the history
* FastZip - fix TimeSetting control when extracting Zip entries
* Add tests for FastZip file times

Co-authored-by: va4es2 <va4es2@users.noreply.github.com>

ref #555 #566
  • Loading branch information
piksel committed Mar 7, 2021
1 parent c959373 commit 765eb69
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 3 deletions.
92 changes: 90 additions & 2 deletions src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.IO;
using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;

namespace ICSharpCode.SharpZipLib.Zip
{
Expand Down Expand Up @@ -195,6 +196,29 @@ public FastZip()
{
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="TimeSetting"/>
/// </summary>
/// <param name="timeSetting">The <see cref="TimeSetting">time setting</see> to use when creating or extracting <see cref="ZipEntry">Zip entries</see>.</param>
/// <remarks>Using <see cref="TimeSetting.LastAccessTime">TimeSetting.LastAccessTime</see><see cref="TimeSetting.LastAccessTimeUtc">[Utc]</see> when
/// creating an archive will set the file time to the moment of reading.
/// </remarks>
public FastZip(TimeSetting timeSetting)
{
entryFactory_ = new ZipEntryFactory(timeSetting);
restoreDateTimeOnExtract_ = true;
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="DateTime"/>
/// </summary>
/// <param name="time">The time to set all <see cref="ZipEntry.DateTime"/> values for created or extracted <see cref="ZipEntry">Zip Entries</see>.</param>
public FastZip(DateTime time)
{
entryFactory_ = new ZipEntryFactory(time);
restoreDateTimeOnExtract_ = true;
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/>
/// </summary>
Expand Down Expand Up @@ -735,7 +759,39 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)

if (restoreDateTimeOnExtract_)
{
File.SetLastWriteTime(targetName, entry.DateTime);
switch (entryFactory_.Setting)
{
case TimeSetting.CreateTime:
File.SetCreationTime(targetName, entry.DateTime);
break;

case TimeSetting.CreateTimeUtc:
File.SetCreationTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.LastAccessTime:
File.SetLastAccessTime(targetName, entry.DateTime);
break;

case TimeSetting.LastAccessTimeUtc:
File.SetLastAccessTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.LastWriteTime:
File.SetLastWriteTime(targetName, entry.DateTime);
break;

case TimeSetting.LastWriteTimeUtc:
File.SetLastWriteTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.Fixed:
File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime);
break;

default:
throw new ZipException("Unhandled time setting in ExtractFileEntry");
}
}

if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1))
Expand Down Expand Up @@ -809,7 +865,39 @@ private void ExtractEntry(ZipEntry entry)
Directory.CreateDirectory(dirName);
if (entry.IsDirectory && restoreDateTimeOnExtract_)
{
Directory.SetLastWriteTime(dirName, entry.DateTime);
switch (entryFactory_.Setting)
{
case TimeSetting.CreateTime:
Directory.SetCreationTime(dirName, entry.DateTime);
break;

case TimeSetting.CreateTimeUtc:
Directory.SetCreationTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.LastAccessTime:
Directory.SetLastAccessTime(dirName, entry.DateTime);
break;

case TimeSetting.LastAccessTimeUtc:
Directory.SetLastAccessTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.LastWriteTime:
Directory.SetLastWriteTime(dirName, entry.DateTime);
break;

case TimeSetting.LastWriteTimeUtc:
Directory.SetLastWriteTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.Fixed:
Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime);
break;

default:
throw new ZipException("Unhandled time setting in ExtractEntry");
}
}
}
else
Expand Down
13 changes: 13 additions & 0 deletions src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using ICSharpCode.SharpZipLib.Core;
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;

namespace ICSharpCode.SharpZipLib.Zip
{
Expand Down Expand Up @@ -50,5 +52,16 @@ public interface IEntryFactory
/// Get/set the <see cref="INameTransform"></see> applicable.
/// </summary>
INameTransform NameTransform { get; set; }

/// <summary>
/// Get the <see cref="TimeSetting"/> in use.
/// </summary>
TimeSetting Setting { get; }

/// <summary>
/// Get the <see cref="DateTime"/> value to use when <see cref="Setting"/> is set to <see cref="TimeSetting.Fixed"/>,
/// or if not specified, the value of <see cref="DateTime.Now"/> when the class was the initialized
/// </summary>
DateTime FixedDateTime { get; }
}
}
2 changes: 1 addition & 1 deletion src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem)

private INameTransform nameTransform_;
private DateTime fixedDateTime_ = DateTime.Now;
private TimeSetting timeSetting_;
private TimeSetting timeSetting_ = TimeSetting.LastWriteTime;
private bool isUnicodeText_;

private int getAttributes_ = -1;
Expand Down
175 changes: 175 additions & 0 deletions test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Text;
using TimeSetting = ICSharpCode.SharpZipLib.Zip.ZipEntryFactory.TimeSetting;

namespace ICSharpCode.SharpZipLib.Tests.Zip
{
Expand Down Expand Up @@ -685,5 +686,179 @@ public void CreateZipShouldLeaveOutputStreamOpenIfRequested(bool leaveOpen)
}
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[Test]
public void CreateZipShouldSetTimeOnEntriesFromConstructorDateTime()
{
var targetTime = TestTargetTime(TimeSetting.Fixed);
var fastZip = new FastZip(targetTime);
var target = CreateFastZipTestArchiveWithAnEntry(fastZip);
var archive = new MemoryStream(target.ToArray());
using (var zf = new ZipFile(archive))
{
Assert.AreEqual(targetTime, zf[0].DateTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
public void CreateZipShouldSetTimeOnEntriesFromConstructorTimeSetting(TimeSetting timeSetting)
{
var targetTime = TestTargetTime(timeSetting);
var fastZip = new FastZip(timeSetting);

var alterTime = (Action<FileInfo>) null;
switch(timeSetting)
{
case TimeSetting.LastWriteTime: alterTime = fi => fi.LastWriteTime = targetTime; break;
case TimeSetting.LastWriteTimeUtc: alterTime = fi => fi.LastWriteTimeUtc = targetTime; break;
case TimeSetting.CreateTime: alterTime = fi => fi.CreationTime = targetTime; break;
case TimeSetting.CreateTimeUtc: alterTime = fi => fi.CreationTimeUtc = targetTime; break;
}

var target = CreateFastZipTestArchiveWithAnEntry(fastZip, alterTime);
// Check that the file contents are correct in both cases
var archive = new MemoryStream(target.ToArray());
using (var zf = new ZipFile(archive))
{
Assert.AreEqual(TestTargetTime(timeSetting), zf[0].DateTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
[TestCase(TimeSetting.Fixed)]
public void ExtractZipShouldSetTimeOnFilesFromConstructorTimeSetting(TimeSetting timeSetting)
{
var targetTime = ExpectedFixedTime();
var archiveStream = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));

if (timeSetting == TimeSetting.Fixed)
{
Assert.Ignore("Fixed time without specifying a time is undefined");
}

var fastZip = new FastZip(timeSetting);
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(archiveStream, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, timeSetting));
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
public void ExtractZipShouldSetTimeOnFilesFromConstructorDateTime(DateTimeKind dtk)
{
// Create the archive with a fixed "bad" datetime
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(UnexpectedFixedTime(dtk)));

// Extract the archive with a fixed time override
var targetTime = ExpectedFixedTime(dtk);
var fastZip = new FastZip(targetTime);
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
var fileTime = FileTimeFromTimeSetting(fi, TimeSetting.Fixed);
if (fileTime.Kind != dtk) fileTime = fileTime.ToUniversalTime();
Assert.AreEqual(targetTime, fileTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
public void ExtractZipShouldSetTimeOnFilesWithEmptyConstructor(DateTimeKind dtk)
{
// Create the archive with a fixed datetime
var targetTime = ExpectedFixedTime(dtk);
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));

// Extract the archive with an empty constructor
var fastZip = new FastZip();
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, TimeSetting.Fixed));
}
}

private static bool IsLastAccessTime(TimeSetting ts)
=> ts == TimeSetting.LastAccessTime || ts == TimeSetting.LastAccessTimeUtc;

private static DateTime FileTimeFromTimeSetting(FileInfo fi, TimeSetting timeSetting)
{
switch (timeSetting)
{
case TimeSetting.LastWriteTime: return fi.LastWriteTime;
case TimeSetting.LastWriteTimeUtc: return fi.LastWriteTimeUtc;
case TimeSetting.CreateTime: return fi.CreationTime;
case TimeSetting.CreateTimeUtc: return fi.CreationTimeUtc;
case TimeSetting.LastAccessTime: return fi.LastAccessTime;
case TimeSetting.LastAccessTimeUtc: return fi.LastAccessTimeUtc;
case TimeSetting.Fixed: return fi.LastWriteTime;
}

throw new ArgumentException("Invalid TimeSetting", nameof(timeSetting));
}

private static DateTime TestTargetTime(TimeSetting ts)
{
var dtk = ts == TimeSetting.CreateTimeUtc
|| ts == TimeSetting.LastWriteTimeUtc
|| ts == TimeSetting.LastAccessTimeUtc
? DateTimeKind.Utc
: DateTimeKind.Local;

return IsLastAccessTime(ts)
// AccessTime will be altered by reading/writing the file entry
? CurrentTime(dtk)
: ExpectedFixedTime(dtk);
}

private static DateTime CurrentTime(DateTimeKind kind)
{
var now = kind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now;
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, (now.Second / 2) * 2, kind);
}

private static DateTime ExpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
=> new DateTime(2010, 5, 30, 16, 22, 50, dtk);
private static DateTime UnexpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
=> new DateTime(1980, 10, 11, 22, 39, 30, dtk);

private const string SingleEntryFileName = "testEntry.dat";

private static TrackedMemoryStream CreateFastZipTestArchiveWithAnEntry(FastZip fastZip, Action<FileInfo> alterFile = null)
{
var target = new TrackedMemoryStream();

using (var tempFolder = new Utils.TempDir())
{

// Create test input file
var addFile = Path.Combine(tempFolder.Fullpath, SingleEntryFileName);
MakeTempFile(addFile, 16);
var fi = new FileInfo(addFile);
alterFile?.Invoke(fi);

fastZip.CreateZip(target, tempFolder.Fullpath, false, SingleEntryFileName, null, leaveOpen: true);
}

return target;
}
}
}

0 comments on commit 765eb69

Please sign in to comment.