-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Compression.ZipFile support for Unix Permissions (#55531)
* Compression.ZipFile support for Unix Permissions When running on Unix, capture the file's permissions on ZipFile Create and write the captured file permissions on ZipFile Extract. Fix #1548
- Loading branch information
Showing
9 changed files
with
299 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
...Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.IO.Compression | ||
{ | ||
public static partial class ZipFileExtensions | ||
{ | ||
static partial void SetExternalAttributes(FileStream fs, ZipArchiveEntry entry) | ||
{ | ||
Interop.Sys.FileStatus status; | ||
Interop.CheckIo(Interop.Sys.FStat(fs.SafeFileHandle, out status), fs.Name); | ||
|
||
entry.ExternalAttributes |= status.Mode << 16; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
...ssion.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.IO.Compression | ||
{ | ||
public static partial class ZipFileExtensions | ||
{ | ||
static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry entry) | ||
{ | ||
// Only extract USR, GRP, and OTH file permissions, and ignore | ||
// S_ISUID, S_ISGID, and S_ISVTX bits. This matches unzip's default behavior. | ||
// It is off by default because of this comment: | ||
|
||
// "It's possible that a file in an archive could have one of these bits set | ||
// and, unknown to the person unzipping, could allow others to execute the | ||
// file as the user or group. The new option -K bypasses this check." | ||
const int ExtractPermissionMask = 0x1FF; | ||
int permissions = (entry.ExternalAttributes >> 16) & ExtractPermissionMask; | ||
|
||
// If the permissions weren't set at all, don't write the file's permissions, | ||
// since the .zip could have been made using a previous version of .NET, which didn't | ||
// include the permissions, or was made on Windows. | ||
if (permissions != 0) | ||
{ | ||
Interop.CheckIo(Interop.Sys.FChMod(fs.SafeFileHandle, permissions), fs.Name); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Text; | ||
using Xunit; | ||
|
||
namespace System.IO.Compression.Tests | ||
{ | ||
public class ZipFile_Unix : ZipFileTestBase | ||
{ | ||
[Fact] | ||
public void UnixCreateSetsPermissionsInExternalAttributes() | ||
{ | ||
// '7600' tests that S_ISUID, S_ISGID, and S_ISVTX bits get preserved in ExternalAttributes | ||
string[] testPermissions = new[] { "777", "755", "644", "600", "7600" }; | ||
|
||
using (var tempFolder = new TempDirectory(Path.Combine(GetTestFilePath(), "testFolder"))) | ||
{ | ||
foreach (string permission in testPermissions) | ||
{ | ||
CreateFile(tempFolder.Path, permission); | ||
} | ||
|
||
string archivePath = GetTestFilePath(); | ||
ZipFile.CreateFromDirectory(tempFolder.Path, archivePath); | ||
|
||
using (ZipArchive archive = ZipFile.OpenRead(archivePath)) | ||
{ | ||
Assert.Equal(5, archive.Entries.Count); | ||
|
||
foreach (ZipArchiveEntry entry in archive.Entries) | ||
{ | ||
Assert.EndsWith(".txt", entry.Name, StringComparison.Ordinal); | ||
EnsureExternalAttributes(entry.Name.Substring(0, entry.Name.Length - 4), entry); | ||
} | ||
|
||
void EnsureExternalAttributes(string permissions, ZipArchiveEntry entry) | ||
{ | ||
Assert.Equal(Convert.ToInt32(permissions, 8), (entry.ExternalAttributes >> 16) & 0xFFF); | ||
} | ||
} | ||
|
||
// test that round tripping the archive has the same file permissions | ||
using (var extractFolder = new TempDirectory(Path.Combine(GetTestFilePath(), "extract"))) | ||
{ | ||
ZipFile.ExtractToDirectory(archivePath, extractFolder.Path); | ||
|
||
foreach (string permission in testPermissions) | ||
{ | ||
string filename = Path.Combine(extractFolder.Path, permission + ".txt"); | ||
Assert.True(File.Exists(filename)); | ||
|
||
EnsureFilePermissions(filename, permission); | ||
} | ||
} | ||
} | ||
} | ||
|
||
[Fact] | ||
public void UnixExtractSetsFilePermissionsFromExternalAttributes() | ||
{ | ||
// '7600' tests that S_ISUID, S_ISGID, and S_ISVTX bits don't get extracted to file permissions | ||
string[] testPermissions = new[] { "777", "755", "644", "754", "7600" }; | ||
byte[] contents = Encoding.UTF8.GetBytes("contents"); | ||
|
||
string archivePath = GetTestFilePath(); | ||
using (FileStream fileStream = new FileStream(archivePath, FileMode.CreateNew)) | ||
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create)) | ||
{ | ||
foreach (string permission in testPermissions) | ||
{ | ||
ZipArchiveEntry entry = archive.CreateEntry(permission + ".txt"); | ||
entry.ExternalAttributes = Convert.ToInt32(permission, 8) << 16; | ||
using Stream stream = entry.Open(); | ||
stream.Write(contents); | ||
stream.Flush(); | ||
} | ||
} | ||
|
||
using (var tempFolder = new TempDirectory(GetTestFilePath())) | ||
{ | ||
ZipFile.ExtractToDirectory(archivePath, tempFolder.Path); | ||
|
||
foreach (string permission in testPermissions) | ||
{ | ||
string filename = Path.Combine(tempFolder.Path, permission + ".txt"); | ||
Assert.True(File.Exists(filename)); | ||
|
||
EnsureFilePermissions(filename, permission); | ||
} | ||
} | ||
} | ||
|
||
private static void CreateFile(string folderPath, string permissions) | ||
{ | ||
string filename = Path.Combine(folderPath, $"{permissions}.txt"); | ||
File.WriteAllText(filename, "contents"); | ||
|
||
Assert.Equal(0, Interop.Sys.ChMod(filename, Convert.ToInt32(permissions, 8))); | ||
} | ||
|
||
private static void EnsureFilePermissions(string filename, string permissions) | ||
{ | ||
Interop.Sys.FileStatus status; | ||
Assert.Equal(0, Interop.Sys.Stat(filename, out status)); | ||
|
||
// note that we don't extract S_ISUID, S_ISGID, and S_ISVTX bits, | ||
// so only use the last 3 numbers of permissions to verify the file permissions | ||
permissions = permissions.Length > 3 ? permissions.Substring(permissions.Length - 3) : permissions; | ||
Assert.Equal(Convert.ToInt32(permissions, 8), status.Mode & 0xFFF); | ||
} | ||
|
||
[Theory] | ||
[InlineData("sharpziplib.zip", null)] // ExternalAttributes are not set in this .zip, use the system default | ||
[InlineData("Linux_RW_RW_R__.zip", "664")] | ||
[InlineData("Linux_RWXRW_R__.zip", "764")] | ||
[InlineData("OSX_RWXRW_R__.zip", "764")] | ||
public void UnixExtractFilePermissionsCompat(string zipName, string expectedPermissions) | ||
{ | ||
expectedPermissions = GetExpectedPermissions(expectedPermissions); | ||
|
||
string zipFileName = compat(zipName); | ||
using (var tempFolder = new TempDirectory(GetTestFilePath())) | ||
{ | ||
ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); | ||
|
||
using ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read); | ||
foreach (ZipArchiveEntry entry in archive.Entries) | ||
{ | ||
string filename = Path.Combine(tempFolder.Path, entry.FullName); | ||
Assert.True(File.Exists(filename), $"File '{filename}' should exist"); | ||
|
||
EnsureFilePermissions(filename, expectedPermissions); | ||
} | ||
} | ||
} | ||
|
||
private static string GetExpectedPermissions(string expectedPermissions) | ||
{ | ||
if (string.IsNullOrEmpty(expectedPermissions)) | ||
{ | ||
// Create a new file, and get its permissions to get the current system default permissions | ||
|
||
using (var tempFolder = new TempDirectory()) | ||
{ | ||
string filename = Path.Combine(tempFolder.Path, Path.GetRandomFileName()); | ||
File.WriteAllText(filename, "contents"); | ||
|
||
Interop.Sys.FileStatus status; | ||
Assert.Equal(0, Interop.Sys.Stat(filename, out status)); | ||
|
||
expectedPermissions = Convert.ToString(status.Mode & 0xFFF, 8); | ||
} | ||
} | ||
|
||
return expectedPermissions; | ||
} | ||
} | ||
} |