Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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>
<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>
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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>
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}
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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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" />
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,5 +450,28 @@ private static async Task UpdateArchive(ZipArchive archive, string installFile,
}
}
}

[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);
}
}
}
}
}
}
159 changes: 159 additions & 0 deletions src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
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;
}
}
}