From 7887adaba00167ed0f9d169a0bc6e3c6dc40c00f Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Tue, 21 Mar 2023 23:01:38 +0000 Subject: [PATCH 01/10] Copy UnixFilePermissions for Zip Compression --- src/ZipArchive.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs index 4c74147..11d9c4e 100644 --- a/src/ZipArchive.cs +++ b/src/ZipArchive.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.IO; using System.IO.Compression; +using System.Runtime.InteropServices; using System.Text; namespace Microsoft.PowerShell.Archive @@ -68,7 +70,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) entryName += ZipArchiveDirectoryPathTerminator; } - _zipArchive.CreateEntry(entryName); + System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntry(entryName); + + CopyUnixFilePermissions(entry, addition.FileSystemInfo); } } else @@ -80,7 +84,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) } // TODO: Add exception handling - _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); + System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); + + CopyUnixFilePermissions(entry, addition.FileSystemInfo); } } @@ -105,6 +111,14 @@ private static System.IO.Compression.ZipArchiveMode ConvertToZipArchiveMode(Arch } } + private static void CopyUnixFilePermissions(ZipArchiveEntry archiveEntry, FileSystemInfo fileSystemInfo) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + archiveEntry.ExternalAttributes |= (int)fileSystemInfo.UnixFileMode; + } + } + protected virtual void Dispose(bool disposing) { if (!_disposedValue) From 576b476e27fd98a54082096dc2f6efaec7b3a26d Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Wed, 22 Mar 2023 23:14:57 +0000 Subject: [PATCH 02/10] Add .idea folder to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6fbb448..a2e8680 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,5 @@ StyleCop.Cache test/tools/Modules/SelfSignedCertificate/ # BenchmarkDotNet artifacts -test/perf/BenchmarkDotNet.Artifacts/ \ No newline at end of file +test/perf/BenchmarkDotNet.Artifacts/ +.idea/ \ No newline at end of file From 07595499e85a8cda6e3fe2115bbc954d891d9d78 Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Sun, 26 Mar 2023 19:29:22 +0100 Subject: [PATCH 03/10] Start adding tests for unix permissions --- ...hould-BeZipArchiveWithUnixPermissions.psm1 | 135 ++++++++++++++++++ Tests/Compress-Archive.Tests.ps1 | 25 ++++ 2 files changed, 160 insertions(+) create mode 100644 Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 diff --git a/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 b/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 new file mode 100644 index 0000000..3f321a8 --- /dev/null +++ b/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 @@ -0,0 +1,135 @@ +function Should-BeZipArchiveWithUnixPermissions { + <# + .SYNOPSIS + Checks if a zip archive contains entries with the expected Unix permissions + .EXAMPLE + "C:\Users\\archive.zip" | Should -BeZipArchiveWithUnixPermissions "d---------" "-rw-------" + + Checks if archive.zip only contains file1.txt + #> + + [CmdletBinding()] + Param ( + [string] $ActualValue, + [string] $TempDirectory, + [string] $ExpectedDirectoryPermissions, + [string] $ExpectedFilePermissions, + [switch] $Negate, + [string] $Because, + [switch] $LiteralPath, + $CallerSessionState + ) + + # We need to ensure that ls won't run Get-ChildItem instead + $previousAlias = Get-Alias ls -ErrorAction SilentlyContinue + if ($previousAlias -ne $null) { + Remove-Alias ls + } + + try { + # ActualValue is supposed to be a path to an archive + # It could be a path to a custom PSDrive, so it needes to be converted + if ($LiteralPath) { + $ActualValue = Convert-Path -LiteralPath $ActualValue + } + else { + $ActualValue = Convert-Path -Path $ActualValue + } + + + # Ensure ActualValue is a valid path + if ($LiteralPath) { + $testPathResult = Test-Path -LiteralPath $ActualValue + } + else { + $testPathResult = Test-Path -Path $ActualValue + } + + # Don't continue processing if ActualValue is not an actual path + if (-not $testPathResult) { + return [pscustomobject]@{ + Succeeded = $false + FailureMessage = $failureMessage + } + } + + $unzipPath = "$TempDirectory/unzipped" + + unzip $ActualValue -d $unzipPath + + # Get ls to list the unzipped contents of the archive with permissions + $output = ls -Rl $unzipPath + + # Check if the output is null + if ($null -eq $output) { + return [pscustomobject]@{ + Succeeded = $false + FailureMessage = "Archive {0} contains nothing, but it was expected to contain something" + } + } + + # Filter the output line by line + $lines = $output -split [System.Environment]::NewLine + + # Go through each line and split it by whitespace + foreach ($line in $lines) { + + #Skip non-file/directory lines from recursive output + #eg. directory path and total blocks count + #./src/obj/Release/ref: + #total 12 + if (-not $line.StartsWith("-") -and -not $line.StartsWith("d")) { + continue; + } + Write-Host $line + $lineComponents = $line -split " +" + + # Example of some lines: + #-rw-r--r-- 1 owner group 26112 Mar 22 00:36 Microsoft.PowerShell.Archive.dll + #drwxr-xr-x 2 owner group 4096 Mar 22 00:19 ref + + # First component contains attributes + # 2nd component is link count + # 3rd componnent is owner + # 4th component is group + # 5th component is file size + # 6th component is last modified month + # 7th component is last modified day + # 8th component is last modified time + # 9th component is file name + + $permissionString = $lineComponents[0]; + + + if ($permissionString[0] -eq 'd') { + if ($permissionString -ne $expectedDirectoryPermissions) { + return [pscustomobject]@{ + Succeeded = $false + FailureMessage = "Expected directory permissions '$expectedDirectoryPermissions' but got '$permissionString'" + } + } + } + else { + if ($permissionString -ne $expectedFilePermissions) { + return [pscustomobject]@{ + Succeeded = $false + FailureMessage = "Expected directory permissions '$expectedFilePermissions' but got '$permissionString'" + } + } + } + } + + + $ObjProperties = @{ + Succeeded = $true + } + return New-Object PSObject -Property $ObjProperties + } + finally { + if($previousAlias -ne $null) { + Set-Alias ls $previousAlias.Definition + } + } +} + +Add-ShouldOperator -Name BeZipArchiveWithUnixPermissions -InternalName 'Should-BeZipArchiveWithUnixPermissions' -Test ${function:Should-BeZipArchiveWithUnixPermissions} \ No newline at end of file diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 index cfa3ad9..a2a9c17 100644 --- a/Tests/Compress-Archive.Tests.ps1 +++ b/Tests/Compress-Archive.Tests.ps1 @@ -4,6 +4,7 @@ BeforeDiscovery { # Loads and registers custom assertion. Ignores usage of unapproved verb with -DisableNameChecking Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveOnlyContaining.psm1" -DisableNameChecking + Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1" -DisableNameChecking } Describe("Microsoft.PowerShell.Archive tests") { @@ -296,6 +297,30 @@ BeforeDiscovery { Compress-Archive -Path $sourcePath -DestinationPath $destinationPath $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt') } + + It "Validate Unix file permissions are preserved" -Skip:$IsWindows { + Remove-Alias "ls" -Force -ErrorAction Ignore + $testDriveRoot = (Get-PsDrive "TestDrive").Root + Write-Host $testFileRoot + $sourcePath = "TestDrive:/SourceDir" + $sourcePathAbsolute = "$testDriveRoot/SourceDir" + $tempUnzipPath = "$testDriveRoot/unzip" + $destinationPath = "TestDrive:/archive4.zip" + New-Item $tempUnzipPath -Type Directory | Out-Null + + $ExpectedDirectoryPermissions = 'drwxr-xr-x' + $ExpectedFilePermissions = "-rwxr--r--" + + if ($env:TF_BUILD -ne $null) { + $ExpectedDirectoryPermissions = 'd---------' + $ExpectedFilePermissions = "-rwx------" + } + + chmod -R u+rwx "$sourcePathAbsolute" + Compress-Archive -Path $sourcePath -DestinationPath $destinationPath + $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt') + $destinationPath | Should -BeZipArchiveWithUnixPermissions $tempUnzipPath $ExpectedDirectoryPermissions $ExpectedFilePermissions + } } Context "Update tests" -Skip { From 556cdabcf03a55c6d1fd6c4e6799f5db74cd5fca Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 22:00:35 +0100 Subject: [PATCH 04/10] Write out the environment umask --- Tests/Compress-Archive.Tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 index a2a9c17..2d1bfc0 100644 --- a/Tests/Compress-Archive.Tests.ps1 +++ b/Tests/Compress-Archive.Tests.ps1 @@ -310,7 +310,8 @@ BeforeDiscovery { $ExpectedDirectoryPermissions = 'drwxr-xr-x' $ExpectedFilePermissions = "-rwxr--r--" - + $testUmask = iex "umask -S" + Write-Host $testUmask if ($env:TF_BUILD -ne $null) { $ExpectedDirectoryPermissions = 'd---------' $ExpectedFilePermissions = "-rwx------" From ca4280dc8579adb9ad14d3582d86789df80a310b Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:02:03 +0100 Subject: [PATCH 05/10] Fix directory permissions --- src/ZipArchive.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs index 11d9c4e..8ad98e4 100644 --- a/src/ZipArchive.cs +++ b/src/ZipArchive.cs @@ -72,7 +72,7 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntry(entryName); - CopyUnixFilePermissions(entry, addition.FileSystemInfo); + CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(ZipArchiveDirectoryPathTerminator)); } } else @@ -86,7 +86,7 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) // TODO: Add exception handling System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); - CopyUnixFilePermissions(entry, addition.FileSystemInfo); + CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(ZipArchiveDirectoryPathTerminator)); } } @@ -111,11 +111,14 @@ private static System.IO.Compression.ZipArchiveMode ConvertToZipArchiveMode(Arch } } - private static void CopyUnixFilePermissions(ZipArchiveEntry archiveEntry, FileSystemInfo fileSystemInfo) + private static void CopyUnixFilePermissions(ZipArchiveEntry archiveEntry, FileSystemInfo fileSystemInfo, bool isDirectory) { + const int S_IFREG = 0x8000; + const int S_IFDIR = 0x4000; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - archiveEntry.ExternalAttributes |= (int)fileSystemInfo.UnixFileMode; + archiveEntry.ExternalAttributes |= (isDirectory ? S_IFDIR : S_IFREG) | (int)fileSystemInfo.UnixFileMode; } } From 570d4d385d57ba541bc95f4cd56d3c7e491d84bf Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:08:46 +0100 Subject: [PATCH 06/10] Remove umask call --- Tests/Compress-Archive.Tests.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 index 2d1bfc0..a2a9c17 100644 --- a/Tests/Compress-Archive.Tests.ps1 +++ b/Tests/Compress-Archive.Tests.ps1 @@ -310,8 +310,7 @@ BeforeDiscovery { $ExpectedDirectoryPermissions = 'drwxr-xr-x' $ExpectedFilePermissions = "-rwxr--r--" - $testUmask = iex "umask -S" - Write-Host $testUmask + if ($env:TF_BUILD -ne $null) { $ExpectedDirectoryPermissions = 'd---------' $ExpectedFilePermissions = "-rwx------" From 9cb29d3a10a6ac97263a419a6c2c6e403ab5bf0b Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:26:15 +0100 Subject: [PATCH 07/10] Add both slashes for directory --- src/ZipArchive.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs index 8ad98e4..933e144 100644 --- a/src/ZipArchive.cs +++ b/src/ZipArchive.cs @@ -70,9 +70,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) entryName += ZipArchiveDirectoryPathTerminator; } - System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntry(entryName); + var entry = _zipArchive.CreateEntry(entryName); - CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(ZipArchiveDirectoryPathTerminator)); + CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectorySeparatorChar)); } } else @@ -84,9 +84,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition) } // TODO: Add exception handling - System.IO.Compression.ZipArchiveEntry entry = _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); + var entry = _zipArchive.CreateEntryFromFile(sourceFileName: addition.FileSystemInfo.FullName, entryName: entryName, compressionLevel: _compressionLevel); - CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(ZipArchiveDirectoryPathTerminator)); + CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectorySeparatorChar)); } } From b6e0ea4891ef0e163da1f12d7cff80f268bfb3c7 Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:33:04 +0100 Subject: [PATCH 08/10] Chmod unzipped folder --- Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 b/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 index 3f321a8..97c3868 100644 --- a/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 +++ b/Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1 @@ -58,6 +58,7 @@ function Should-BeZipArchiveWithUnixPermissions { unzip $ActualValue -d $unzipPath # Get ls to list the unzipped contents of the archive with permissions + chmod 775 $unzipPath $output = ls -Rl $unzipPath # Check if the output is null From 705c860509601ccf15c753872b5d680d2a3528d8 Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:48:12 +0100 Subject: [PATCH 09/10] Set dir permissions to 775 in test data --- Tests/Compress-Archive.Tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 index a2a9c17..da01234 100644 --- a/Tests/Compress-Archive.Tests.ps1 +++ b/Tests/Compress-Archive.Tests.ps1 @@ -316,7 +316,8 @@ BeforeDiscovery { $ExpectedFilePermissions = "-rwx------" } - chmod -R u+rwx "$sourcePathAbsolute" + find $sourcePathAbsolute -type d -print0 | xargs -0 chmod 775 + find $sourcePathAbsolute -type f -print0 | xargs -0 chmod 700 Compress-Archive -Path $sourcePath -DestinationPath $destinationPath $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt') $destinationPath | Should -BeZipArchiveWithUnixPermissions $tempUnzipPath $ExpectedDirectoryPermissions $ExpectedFilePermissions From 626048fc6e88455647a249f39f657d3885d9d43b Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Mon, 27 Mar 2023 23:59:45 +0100 Subject: [PATCH 10/10] Use join path for absolute paths --- Tests/Compress-Archive.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1 index da01234..0f698ff 100644 --- a/Tests/Compress-Archive.Tests.ps1 +++ b/Tests/Compress-Archive.Tests.ps1 @@ -303,8 +303,8 @@ BeforeDiscovery { $testDriveRoot = (Get-PsDrive "TestDrive").Root Write-Host $testFileRoot $sourcePath = "TestDrive:/SourceDir" - $sourcePathAbsolute = "$testDriveRoot/SourceDir" - $tempUnzipPath = "$testDriveRoot/unzip" + $sourcePathAbsolute = Join-Path $testDriveRoot "SourceDir" + $tempUnzipPath = Join-Path $testDriveRoot "unzip" $destinationPath = "TestDrive:/archive4.zip" New-Item $tempUnzipPath -Type Directory | Out-Null