Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions src/Tasks.UnitTests/Unzip_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public void CanOverwriteReadOnlyFile()
DestinationFolder = new TaskItem(source.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) }
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeTrue(_mockEngine.Log);
Expand Down Expand Up @@ -68,6 +69,7 @@ public void CanUnzip()
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
FailIfNotIncremental = true,
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};
unzip.Execute().ShouldBeFalse(_mockEngine.Log);
_mockEngine.Log = string.Empty;
Expand All @@ -81,6 +83,7 @@ public void CanUnzip()
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
FailIfNotIncremental = false,
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};
unzip2.Execute().ShouldBeTrue(_mockEngine.Log);

Expand All @@ -96,6 +99,7 @@ public void CanUnzip()
SkipUnchangedFiles = true,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
FailIfNotIncremental = true,
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};
unzip3.Execute().ShouldBeTrue(_mockEngine.Log);
}
Expand Down Expand Up @@ -123,7 +127,8 @@ public void CanUnzip_ExplicitDirectoryEntries()
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) }
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeTrue(customMessage: _mockEngine.Log);
Expand All @@ -142,7 +147,8 @@ public void LogsErrorIfDirectoryCannotBeCreated()
Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(String.Empty)
DestinationFolder = new TaskItem(String.Empty),
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -191,7 +197,8 @@ public void LogsErrorIfReadOnlyFileCannotBeOverwitten()
DestinationFolder = new TaskItem(source.Path),
OverwriteReadOnlyFiles = false,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) }
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand All @@ -213,7 +220,8 @@ public void LogsErrorIfSourceFileCannotBeOpened()
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(folder.Path),
SourceFiles = new ITaskItem[] { new TaskItem(file.Path), }
SourceFiles = new ITaskItem[] { new TaskItem(file.Path), },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand All @@ -233,7 +241,8 @@ public void LogsErrorIfSourceFileDoesNotExist()
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(folder.Path),
SourceFiles = new ITaskItem[] { new TaskItem(Path.Combine(testEnvironment.DefaultTestDirectory.Path, "foo.zip")), }
SourceFiles = new ITaskItem[] { new TaskItem(Path.Combine(testEnvironment.DefaultTestDirectory.Path, "foo.zip")), },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -261,7 +270,8 @@ public void CanUnzip_WithIncludeFilter()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "BE78A17D30144B549D21F71D5C633F7D.txt"
Include = "BE78A17D30144B549D21F71D5C633F7D.txt",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeTrue(_mockEngine.Log);
Expand Down Expand Up @@ -290,7 +300,8 @@ public void CanUnzip_WithExcludeFilter()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "BE78A17D30144B549D21F71D5C633F7D.txt"
Exclude = "BE78A17D30144B549D21F71D5C633F7D.txt",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeTrue(_mockEngine.Log);
Expand Down Expand Up @@ -324,7 +335,8 @@ public void CanUnzip_WithIncludeAndExcludeFilter()
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "*.js",
Exclude = "*.js.map;sub\\*.js"
Exclude = "*.js.map;sub\\*.js",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeTrue(_mockEngine.Log);
Expand Down Expand Up @@ -356,7 +368,8 @@ public void LogsErrorIfIncludeContainsInvalidPathCharacters()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "<BE78A17D30144B|549D21F71D5C633F7D/.txt"
Include = "<BE78A17D30144B|549D21F71D5C633F7D/.txt",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -384,7 +397,8 @@ public void LogsErrorIfIncludeContainsPropertyReferences()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "$(Include)"
Include = "$(Include)",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -412,7 +426,8 @@ public void LogsErrorIfExcludeContainsInvalidPathCharacters()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "<BE78A17D30144B|549D21F71D5C633F7D/.txt"
Exclude = "<BE78A17D30144B|549D21F71D5C633F7D/.txt",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -440,7 +455,8 @@ public void LogsErrorIfExcludeContainsPropertyReferences()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "$(Include)"
Exclude = "$(Include)",
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};

unzip.Execute().ShouldBeFalse(_mockEngine.Log);
Expand Down Expand Up @@ -474,6 +490,7 @@ public void CanKeepUnixFilePermissions()
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest()
};
unzip.Execute().ShouldBeTrue(_mockEngine.Log);
string unzippedFilePath = Path.Combine(destination.Path, executableName);
Expand Down
20 changes: 13 additions & 7 deletions src/Tasks/Unzip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace Microsoft.Build.Tasks
/// <summary>
/// Represents a task that can extract a .zip archive.
/// </summary>
public sealed class Unzip : TaskExtension, ICancelableTask, IIncrementalTask
[MSBuildMultiThreadableTask]
public sealed class Unzip : TaskExtension, ICancelableTask, IIncrementalTask, IMultiThreadableTask
{
// We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K).
// The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant
Expand Down Expand Up @@ -75,6 +76,9 @@ public sealed class Unzip : TaskExtension, ICancelableTask, IIncrementalTask

public bool FailIfNotIncremental { get; set; }

/// <inheritdoc />
public TaskEnvironment TaskEnvironment { get; set; }

/// <inheritdoc cref="ICancelableTask.Cancel"/>
public void Cancel()
{
Expand All @@ -84,10 +88,11 @@ public void Cancel()
/// <inheritdoc cref="Task.Execute"/>
public override bool Execute()
{
AbsolutePath destinationPath = TaskEnvironment.GetAbsolutePath(DestinationFolder.ItemSpec);
DirectoryInfo destinationDirectory;
try
{
destinationDirectory = Directory.CreateDirectory(DestinationFolder.ItemSpec);
destinationDirectory = Directory.CreateDirectory(destinationPath);
}
catch (Exception e)
{
Expand All @@ -106,15 +111,16 @@ public override bool Execute()
{
foreach (ITaskItem sourceFile in SourceFiles.TakeWhile(i => !_cancellationToken.IsCancellationRequested))
{
if (!FileSystems.Default.FileExists(sourceFile.ItemSpec))
AbsolutePath sourceFilePath = TaskEnvironment.GetAbsolutePath(sourceFile.ItemSpec);
if (!FileSystems.Default.FileExists(sourceFilePath))
{
Log.LogErrorWithCodeFromResources("Unzip.ErrorFileDoesNotExist", sourceFile.ItemSpec);
continue;
}

try
{
using (FileStream stream = new FileStream(sourceFile.ItemSpec, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false))
using (FileStream stream = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false))
{
#pragma warning disable CA2000 // Dispose objects before losing scope because ZipArchive will dispose the stream when it is disposed.
using (ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false))
Expand Down Expand Up @@ -160,7 +166,7 @@ public override bool Execute()
/// <param name="destinationDirectory">The <see cref="DirectoryInfo"/> to extract files to.</param>
private void Extract(ZipArchive sourceArchive, DirectoryInfo destinationDirectory)
{
string fullDestinationDirectoryPath = Path.GetFullPath(FrameworkFileUtilities.EnsureTrailingSlash(destinationDirectory.FullName));
AbsolutePath fullDestinationDirectoryPath = TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.EnsureTrailingSlash(destinationDirectory.FullName));

foreach (ZipArchiveEntry zipArchiveEntry in sourceArchive.Entries.TakeWhile(i => !_cancellationToken.IsCancellationRequested))
{
Expand All @@ -170,8 +176,8 @@ private void Extract(ZipArchive sourceArchive, DirectoryInfo destinationDirector
continue;
}

string fullDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectory.FullName, zipArchiveEntry.FullName));
ErrorUtilities.VerifyThrowInvalidOperation(fullDestinationPath.StartsWith(fullDestinationDirectoryPath, FileUtilities.PathComparison), "Unzip.ZipSlipExploit", fullDestinationPath);
AbsolutePath fullDestinationPath = TaskEnvironment.GetAbsolutePath(Path.Combine(destinationDirectory.FullName, zipArchiveEntry.FullName));
ErrorUtilities.VerifyThrowInvalidOperation(fullDestinationPath.Value.StartsWith(fullDestinationDirectoryPath.Value, FileUtilities.PathComparison), "Unzip.ZipSlipExploit", fullDestinationPath.Value);

FileInfo destinationPath = new(fullDestinationPath);

Expand Down
Loading