From 60c64ca0378d889b53d3e696c39c0c1c5b451fbf Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 14:12:30 -0500 Subject: [PATCH 1/9] 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 --- .../src/System.IO.Compression.ZipFile.csproj | 16 ++++++++- ...ipFileExtensions.ZipArchive.Create.Unix.cs | 20 +++++++++++ .../ZipFileExtensions.ZipArchive.Create.cs | 6 +++- ...Extensions.ZipArchiveEntry.Extract.Unix.cs | 33 +++++++++++++++++++ ...pFileExtensions.ZipArchiveEntry.Extract.cs | 8 +++-- 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs create mode 100644 src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj index 93eb3e71933e84..238e6a44da7b7d 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj @@ -1,7 +1,7 @@ true - $(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix enable @@ -14,10 +14,24 @@ + + + + + + + + + + diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs new file mode 100644 index 00000000000000..b6a2d236ab804f --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs @@ -0,0 +1,20 @@ +// 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; + if (Interop.Sys.FStat(fs.SafeFileHandle, out status) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw new IOException(error.GetErrorMessage(), error.RawErrno); + } + + entry.ExternalAttributes |= status.Mode << 16; + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs index 00d82c242b0a67..c9199a97f2a895 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs @@ -94,7 +94,7 @@ internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destinatio // Argument checking gets passed down to FileStream's ctor and CreateEntry - using (Stream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) + using (FileStream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) { ZipArchiveEntry entry = compressionLevel.HasValue ? destination.CreateEntry(entryName, compressionLevel.Value) @@ -109,11 +109,15 @@ internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destinatio entry.LastWriteTime = lastWrite; + SetExternalAttributes(fs, entry); + using (Stream es = entry.Open()) fs.CopyTo(es); return entry; } } + + static partial void SetExternalAttributes(FileStream fs, ZipArchiveEntry entry); } } diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs new file mode 100644 index 00000000000000..ee7713dfa67a9f --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs @@ -0,0 +1,33 @@ +// 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) + { + if (Interop.Sys.FChMod(fs.SafeFileHandle, permissions) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw new IOException(error.GetErrorMessage(), error.RawErrno); + } + } + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index cad1a12c5a485b..a74aca915faaf6 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; - namespace System.IO.Compression { public static partial class ZipFileExtensions @@ -75,15 +73,19 @@ public static void ExtractToFile(this ZipArchiveEntry source, string destination // Rely on FileStream's ctor for further checking destinationFileName parameter FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; - using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) + using (FileStream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) { using (Stream es = source.Open()) es.CopyTo(fs); + + ExtractExternalAttributes(fs, source); } File.SetLastWriteTime(destinationFileName, source.LastWriteTime.DateTime); } + static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry entry); + internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName) => ExtractRelativeToDirectory(source, destinationDirectoryName, overwrite: false); From d52fce0136db592d74ca22212744e686401571af Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 18:24:20 -0500 Subject: [PATCH 2/9] Add unit tests --- .../System/IO/Compression/ZipTestHelper.cs | 11 ++++ ...System.IO.Compression.ZipFile.Tests.csproj | 6 +- .../tests/ZipFile.Create.cs | 65 +++++++++++++++++++ .../tests/ZipFile.Extract.cs | 36 ++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs index 6f319461c35ef4..dc3da140a5433f 100644 --- a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs +++ b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs @@ -383,5 +383,16 @@ internal static void AddEntry(ZipArchive archive, string name, string contents, w.WriteLine(contents); } } + + internal 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); + } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index bf9e78ab742854..0e5eda2f243b83 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix @@ -23,6 +23,10 @@ + + + + diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs index 3ba4081397b7bf..50022c7f664c33 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Xunit; @@ -450,5 +451,69 @@ private static async Task UpdateArchive(ZipArchive archive, string installFile, } } } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void Unix_Create_SetsPermissionsInExternalAttributes() + { + // '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); + } + } + } + } + + private static void CreateFile(string folderPath, string permissions) + { + string filename = Path.Combine(folderPath, $"{permissions}.txt"); + File.WriteAllText(filename, "contents"); + ChMod(filename, permissions); + } + + [DllImport("libc")] + private static extern int chmod(string path, int mode); + + private static void ChMod(string filename, string mode) + { + Assert.Equal(0, chmod(filename, Convert.ToInt32(mode, 8))); + } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs index 9bbfd271d2e474..dc24959c4ecaff 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs @@ -171,5 +171,41 @@ public void ExtractToDirectoryZipArchiveOverwrite() } } } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + 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); + } + } + } } } From d4f182115814c28685e7e5164ee4cb7a85a7d9b6 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 18:35:40 -0500 Subject: [PATCH 3/9] Fix build --- .../System/IO/Compression/ZipTestHelper.cs | 11 ---------- ...System.IO.Compression.ZipFile.Tests.csproj | 1 + .../tests/ZipFile.Create.cs | 2 +- .../tests/ZipFile.Extract.cs | 2 +- .../tests/ZipFileHelpers.cs | 21 +++++++++++++++++++ 5 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 src/libraries/System.IO.Compression.ZipFile/tests/ZipFileHelpers.cs diff --git a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs index dc3da140a5433f..6f319461c35ef4 100644 --- a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs +++ b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs @@ -383,16 +383,5 @@ internal static void AddEntry(ZipArchive archive, string name, string contents, w.WriteLine(contents); } } - - internal 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); - } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index 0e5eda2f243b83..089f2b8a6aeb36 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -8,6 +8,7 @@ + 3 ? permissions.Substring(permissions.Length - 3) : permissions; + Assert.Equal(Convert.ToInt32(permissions, 8), status.Mode & 0xFFF); + } + } +} From db4f43a84fc1d5bf7351f1a5a9949e3746481c0d Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 19:05:13 -0500 Subject: [PATCH 4/9] Fix windows build --- ...System.IO.Compression.ZipFile.Tests.csproj | 2 +- .../tests/ZipFile.Create.cs | 65 ---------- .../tests/ZipFile.Extract.cs | 36 ------ .../tests/ZipFile.Unix.cs | 121 ++++++++++++++++++ .../tests/ZipFileHelpers.cs | 21 --- 5 files changed, 122 insertions(+), 123 deletions(-) create mode 100644 src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs delete mode 100644 src/libraries/System.IO.Compression.ZipFile/tests/ZipFileHelpers.cs diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index 089f2b8a6aeb36..7daab45ce5efbd 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -8,7 +8,6 @@ - + diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs index 9682e67e9e4b7c..3ba4081397b7bf 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Xunit; @@ -451,69 +450,5 @@ private static async Task UpdateArchive(ZipArchive archive, string installFile, } } } - - [Fact] - [PlatformSpecific(TestPlatforms.AnyUnix)] - public void Unix_Create_SetsPermissionsInExternalAttributes() - { - // '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)); - - ZipFileHelpers.EnsureFilePermissions(filename, permission); - } - } - } - } - - private static void CreateFile(string folderPath, string permissions) - { - string filename = Path.Combine(folderPath, $"{permissions}.txt"); - File.WriteAllText(filename, "contents"); - ChMod(filename, permissions); - } - - [DllImport("libc")] - private static extern int chmod(string path, int mode); - - private static void ChMod(string filename, string mode) - { - Assert.Equal(0, chmod(filename, Convert.ToInt32(mode, 8))); - } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs index c322f7b0352961..9bbfd271d2e474 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs @@ -171,41 +171,5 @@ public void ExtractToDirectoryZipArchiveOverwrite() } } } - - [Fact] - [PlatformSpecific(TestPlatforms.AnyUnix)] - 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)); - - ZipFileHelpers.EnsureFilePermissions(filename, permission); - } - } - } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs new file mode 100644 index 00000000000000..2c8e083f956842 --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +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"); + ChMod(filename, permissions); + } + + [DllImport("libc")] + private static extern int chmod(string path, int mode); + + private static void ChMod(string filename, string mode) + { + Assert.Equal(0, chmod(filename, Convert.ToInt32(mode, 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); + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFileHelpers.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFileHelpers.cs deleted file mode 100644 index 3da6249bc31eae..00000000000000 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFileHelpers.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.IO.Compression.Tests -{ - public static class ZipFileHelpers - { - internal 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); - } - } -} From 1d012c5386470445bc2d38340c188f3d7a918e67 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 20:51:36 -0500 Subject: [PATCH 5/9] Add Browser TFM --- .../src/System.IO.Compression.ZipFile.csproj | 4 ++-- .../tests/System.IO.Compression.ZipFile.Tests.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj index 238e6a44da7b7d..57946ec6291511 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj @@ -1,7 +1,7 @@ true - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser enable @@ -15,7 +15,7 @@ Link="Common\System\IO\PathInternal.CaseSensitivity.cs" /> - + + - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser @@ -23,7 +23,7 @@ - + From f7f20785fb1f7f51c6c37f9d472e51303f3747ef Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 12 Jul 2021 23:54:11 -0500 Subject: [PATCH 6/9] Fix tests on wasm --- .../tests/System.IO.Compression.ZipFile.Tests.csproj | 1 + .../System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index bc6688798df96e..9fbde72eef7b46 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs index 2c8e083f956842..4d59460ae2e853 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs @@ -96,15 +96,8 @@ private static void CreateFile(string folderPath, string permissions) { string filename = Path.Combine(folderPath, $"{permissions}.txt"); File.WriteAllText(filename, "contents"); - ChMod(filename, permissions); - } - - [DllImport("libc")] - private static extern int chmod(string path, int mode); - private static void ChMod(string filename, string mode) - { - Assert.Equal(0, chmod(filename, Convert.ToInt32(mode, 8))); + Assert.Equal(0, Interop.Sys.ChMod(filename, Convert.ToInt32(permissions, 8))); } private static void EnsureFilePermissions(string filename, string permissions) From 04e929efb728a34d3581a6a2f1e993e73e26634d Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 13 Jul 2021 11:13:24 -0500 Subject: [PATCH 7/9] PR feedback. Add more ZipFile tests --- .../src/System.IO.Compression.ZipFile.csproj | 2 ++ ...ipFileExtensions.ZipArchive.Create.Unix.cs | 3 +-- ...Extensions.ZipArchiveEntry.Extract.Unix.cs | 3 +-- .../tests/ZipFile.Create.cs | 23 ++++++++++++++++++ .../tests/ZipFile.Unix.cs | 24 ++++++++++++++++++- 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj index 57946ec6291511..2110ae810ea63f 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj @@ -18,6 +18,8 @@ + 3 ? permissions.Substring(permissions.Length - 3) : permissions; Assert.Equal(Convert.ToInt32(permissions, 8), status.Mode & 0xFFF); } + + [Theory] + [InlineData("sharpziplib.zip", "644")] + [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) + { + 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); + } + } + } } } From fe69ae74c760567eafebfbe0071ad7b1de899bba Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 13 Jul 2021 12:10:57 -0500 Subject: [PATCH 8/9] Fix build --- .../src/Resources/Strings.resx | 38 ++++++++++++++++++- ...ipFileExtensions.ZipArchive.Create.Unix.cs | 5 +-- ...Extensions.ZipArchiveEntry.Extract.Unix.cs | 5 +-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx b/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx index 5d25044f7b4871..b8f50177b395ee 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx @@ -57,10 +57,46 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Specified file length was too large for the file system. + Zip entry name ends in directory separator character but contains data. Extracting Zip entry would have resulted in a file outside the specified destination directory. - \ No newline at end of file + + The file '{0}' already exists. + + + Unable to find the specified file. + + + Could not find file '{0}'. + + + Could not find a part of the path. + + + Could not find a part of the path '{0}'. + + + The specified file name or path is too long, or a component of the specified path is too long. + + + The path '{0}' is too long, or a component of the specified path is too long. + + + The process cannot access the file '{0}' because it is being used by another process. + + + The process cannot access the file because it is being used by another process. + + + Access to the path is denied. + + + Access to the path '{0}' is denied. + + diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs index 8cf95dbb242537..d2e4f44a6751af 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs @@ -8,10 +8,7 @@ public static partial class ZipFileExtensions static partial void SetExternalAttributes(FileStream fs, ZipArchiveEntry entry) { Interop.Sys.FileStatus status; - if (Interop.Sys.FStat(fs.SafeFileHandle, out status) != 0) - { - Interop.CheckIO(Interop.Sys.GetLastErrorInfo(), fs.Name); - } + Interop.CheckIo(Interop.Sys.FStat(fs.SafeFileHandle, out status), fs.Name); entry.ExternalAttributes |= status.Mode << 16; } diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs index f3aff60f4b5e78..4da8b87b43ea33 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs @@ -22,10 +22,7 @@ static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry ent // include the permissions, or was made on Windows. if (permissions != 0) { - if (Interop.Sys.FChMod(fs.SafeFileHandle, permissions) != 0) - { - Interop.CheckIO(Interop.Sys.GetLastErrorInfo(), fs.Name); - } + Interop.CheckIo(Interop.Sys.FChMod(fs.SafeFileHandle, permissions), fs.Name); } } } From 5b570400393fe0e67f9603509e197a90807c2639 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 13 Jul 2021 17:01:23 -0500 Subject: [PATCH 9/9] Fix test on wasm --- .../tests/ZipFile.Unix.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs index a9bf0d9c9844ce..ab61ae92443d58 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs @@ -111,12 +111,14 @@ private static void EnsureFilePermissions(string filename, string permissions) } [Theory] - [InlineData("sharpziplib.zip", "644")] + [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())) { @@ -132,5 +134,26 @@ public void UnixExtractFilePermissionsCompat(string zipName, string expectedPerm } } } + + 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; + } } }