Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEric Erhardt <eric.erhardt@microsoft.com>2021-07-14 07:14:24 +0300
committerGitHub <noreply@github.com>2021-07-14 07:14:24 +0300
commit5b0c6dd5756eea63a57b1f69b182f7386da0f631 (patch)
tree0cadbafde08d75b9993efeb7766bb85df3fc3427 /src
parent6a7603e4edfd273df69726c8e589a121bd165649 (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx38
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj18
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs16
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs6
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs29
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs8
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj10
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs23
-rw-r--r--src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs159
9 files changed, 299 insertions, 8 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 5d25044f7b4..b8f50177b39 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 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="ArgumentOutOfRange_FileLengthTooBig" xml:space="preserve">
+ <value>Specified file length was too large for the file system.</value>
+ </data>
<data name="IO_DirectoryNameWithData" xml:space="preserve">
<value>Zip entry name ends in directory separator character but contains data.</value>
</data>
<data name="IO_ExtractingResultsInOutside" xml:space="preserve">
<value>Extracting Zip entry would have resulted in a file outside the specified destination directory.</value>
</data>
-</root> \ No newline at end of file
+ <data name="IO_FileExists_Name" xml:space="preserve">
+ <value>The file '{0}' already exists.</value>
+ </data>
+ <data name="IO_FileNotFound" xml:space="preserve">
+ <value>Unable to find the specified file.</value>
+ </data>
+ <data name="IO_FileNotFound_FileName" xml:space="preserve">
+ <value>Could not find file '{0}'.</value>
+ </data>
+ <data name="IO_PathNotFound_NoPathName" xml:space="preserve">
+ <value>Could not find a part of the path.</value>
+ </data>
+ <data name="IO_PathNotFound_Path" xml:space="preserve">
+ <value>Could not find a part of the path '{0}'.</value>
+ </data>
+ <data name="IO_PathTooLong" xml:space="preserve">
+ <value>The specified file name or path is too long, or a component of the specified path is too long.</value>
+ </data>
+ <data name="IO_PathTooLong_Path" xml:space="preserve">
+ <value>The path '{0}' is too long, or a component of the specified path is too long.</value>
+ </data>
+ <data name="IO_SharingViolation_File" xml:space="preserve">
+ <value>The process cannot access the file '{0}' because it is being used by another process.</value>
+ </data>
+ <data name="IO_SharingViolation_NoFileName" xml:space="preserve">
+ <value>The process cannot access the file because it is being used by another process.</value>
+ </data>
+ <data name="UnauthorizedAccess_IODenied_NoPathName" xml:space="preserve">
+ <value>Access to the path is denied.</value>
+ </data>
+ <data name="UnauthorizedAccess_IODenied_Path" xml:space="preserve">
+ <value>Access to the path '{0}' is denied.</value>
+ </data>
+</root>
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 93eb3e71933..2110ae810ea 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 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
+ <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -14,10 +14,26 @@
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
</ItemGroup>
+ <!-- Unix specific files -->
+ <ItemGroup Condition=" '$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' ">
+ <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchive.Create.Unix.cs" />
+ <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
+ Link="Common\Interop\Unix\Interop.IOErrors.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
+ Link="Common\Interop\Unix\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
+ Link="Common\Interop\Unix\System.Native\Interop.Errors.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FChMod.cs"
+ Link="Common\Interop\Unix\System.Native\Interop.FChMod.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
+ Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
+ </ItemGroup>
<ItemGroup>
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.FileSystem" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
+ <Reference Include="System.Runtime.InteropServices" />
</ItemGroup>
</Project>
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 00000000000..d2e4f44a675
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Unix.cs
@@ -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;
+ }
+ }
+}
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 00d82c242b0..c9199a97f2a 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 @@ namespace System.IO.Compression
// 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 @@ namespace System.IO.Compression
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 00000000000..4da8b87b43e
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs
@@ -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);
+ }
+ }
+ }
+}
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 cad1a12c5a4..a74aca915fa 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 @@ namespace System.IO.Compression
// 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);
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 bf9e78ab742..9fbde72eef7 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 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
+ <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="ZipFile.Create.cs" />
@@ -23,6 +23,12 @@
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs"
Link="Common\System\IO\Compression\ZipTestHelper.cs" />
</ItemGroup>
+ <ItemGroup Condition=" '$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' ">
+ <Compile Include="ZipFile.Unix.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ChMod.cs" Link="Interop\Unix\System.Native\Interop.ChMod.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
+ </ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Compression.TestData" Version="$(SystemIOCompressionTestDataVersion)" />
</ItemGroup>
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 3ba4081397b..18768b5a592 100644
--- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs
@@ -450,5 +450,28 @@ namespace System.IO.Compression.Tests
}
}
}
+
+ [Fact]
+ public void CreateSetsExternalAttributesCorrectly()
+ {
+ string folderName = zfolder("normal");
+ string filepath = GetTestFilePath();
+ ZipFile.CreateFromDirectory(folderName, filepath);
+
+ using (ZipArchive archive = ZipFile.Open(filepath, ZipArchiveMode.Read))
+ {
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ Assert.Equal(0, entry.ExternalAttributes);
+ }
+ else
+ {
+ Assert.NotEqual(0, entry.ExternalAttributes);
+ }
+ }
+ }
+ }
}
}
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 00000000000..ab61ae92443
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
@@ -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;
+ }
+ }
+}