Skip to content

Copy Unix File Permissions In Zip #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@ StyleCop.Cache
test/tools/Modules/SelfSignedCertificate/

# BenchmarkDotNet artifacts
test/perf/BenchmarkDotNet.Artifacts/
test/perf/BenchmarkDotNet.Artifacts/
.idea/
136 changes: 136 additions & 0 deletions Tests/Assertions/Should-BeZipArchiveWithUnixPermissions.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
function Should-BeZipArchiveWithUnixPermissions {
<#
.SYNOPSIS
Checks if a zip archive contains entries with the expected Unix permissions
.EXAMPLE
"C:\Users\<user>\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
chmod 775 $unzipPath
$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}
26 changes: 26 additions & 0 deletions Tests/Compress-Archive.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -296,6 +297,31 @@ 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 = Join-Path $testDriveRoot "SourceDir"
$tempUnzipPath = Join-Path $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------"
}

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
}
}

Context "Update tests" -Skip {
Expand Down
21 changes: 19 additions & 2 deletions src/ZipArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,7 +70,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition)
entryName += ZipArchiveDirectoryPathTerminator;
}

_zipArchive.CreateEntry(entryName);
var entry = _zipArchive.CreateEntry(entryName);

CopyUnixFilePermissions(entry, addition.FileSystemInfo, entryName.EndsWith(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectorySeparatorChar));
}
}
else
Expand All @@ -80,7 +84,9 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition)
}

// TODO: Add exception handling
_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(Path.DirectorySeparatorChar) || entryName.EndsWith(Path.AltDirectorySeparatorChar));
}
}

Expand All @@ -105,6 +111,17 @@ private static System.IO.Compression.ZipArchiveMode ConvertToZipArchiveMode(Arch
}
}

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 |= (isDirectory ? S_IFDIR : S_IFREG) | (int)fileSystemInfo.UnixFileMode;
}
}

protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
Expand Down