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 de869f13e93532..32e34d7d6493f7 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)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent) enable @@ -16,8 +16,12 @@ + + + + - + + 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 ef6192cb903166..f3cf37712c54ae 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 @@ -96,7 +96,7 @@ internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source!!, s if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar)) destinationDirectoryFullPath += Path.DirectorySeparatorChar; - string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, source.FullName)); + string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, SanitizeZipFilePath(source.FullName))); if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison)) throw new IOException(SR.IO_ExtractingResultsInOutside); diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidFullName_Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidFullName_Unix.cs new file mode 100644 index 00000000000000..210739d3b3aa8a --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidFullName_Unix.cs @@ -0,0 +1,15 @@ +// 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; + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + internal static string SanitizeZipFilePath(string zipPath) + { + return zipPath.Replace('\0', '_'); + } + } +} diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidName_Windows.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidName_Windows.cs new file mode 100644 index 00000000000000..6ab389dce61095 --- /dev/null +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileValidName_Windows.cs @@ -0,0 +1,28 @@ +// 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; + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + internal static string SanitizeZipFilePath(string zipPath) + { + StringBuilder builder = new StringBuilder(zipPath); + for (int i = 0; i < zipPath.Length; i++) + { + if (((int)builder[i] >= 0 && (int)builder[i] < 32) || + builder[i] == '?' || builder[i] == ':' || + builder[i] == '*' || builder[i] == '"' || + builder[i] == '<' || builder[i] == '>' || + builder[i] == '|') + { + builder[i] = '_'; + } + } + return builder.ToString(); + } + } +} 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 18768b5a592fcd..25bd7c65ec0619 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs @@ -288,7 +288,6 @@ public void InvalidDates() FileInfo fileWithBadDate = new FileInfo(GetTestFilePath()); fileWithBadDate.Create().Dispose(); fileWithBadDate.LastWriteTimeUtc = new DateTime(1970, 1, 1, 1, 1, 1); - string archivePath = GetTestFilePath(); using (FileStream output = File.Open(archivePath, FileMode.Create)) using (ZipArchive archive = new ZipArchive(output, ZipArchiveMode.Create)) @@ -344,19 +343,6 @@ public void ReadStreamOps() } } - /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. - /// - [Theory] - [InlineData("NullCharFileName_FromWindows")] - [InlineData("NullCharFileName_FromUnix")] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path - public void Unix_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName) - { - Assert.Throws(() => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); - } - [Fact] public void UpdateReadTwice() { @@ -415,24 +401,6 @@ public async Task UpdateAddFile() } } - /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. - /// - [Theory] - [InlineData("WindowsInvalid_FromUnix", null)] - [InlineData("WindowsInvalid_FromWindows", null)] - [InlineData("NullCharFileName_FromWindows", "path")] - [InlineData("NullCharFileName_FromUnix", "path")] - [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path - public void Windows_ZipWithInvalidFileNames_ThrowsException(string zipName, string paramName) - { - if (paramName == null) - Assert.Throws(() => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); - else - AssertExtensions.Throws(paramName, null, () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); - } - private static async Task UpdateArchive(ZipArchive archive, string installFile, string entryName) { string fileName = installFile; 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 36dafcd259fe39..b9517e975088a5 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs @@ -70,9 +70,12 @@ public void ExtractOutOfRoot(string entryName) [InlineData("NullCharFileName_FromWindows")] [InlineData("NullCharFileName_FromUnix")] [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path - public void Unix_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName) + public void Unix_ZipWithInvalidFileNames(string zipName) { - AssertExtensions.Throws("path", () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + var testDirectory = GetTestFilePath(); + ZipFile.ExtractToDirectory(compat(zipName) + ".zip", testDirectory); + + Assert.True(File.Exists(Path.Combine(testDirectory, "a_6b6d"))); } [Theory] @@ -90,30 +93,47 @@ public void Unix_ZipWithOSSpecificFileNames(string zipName, string fileName) Assert.Equal(fileName, Path.GetFileName(results[0])); } - /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. - /// - [Theory] - [InlineData("NullCharFileName_FromWindows")] - [InlineData("NullCharFileName_FromUnix")] - [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path - public void Windows_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName) - { - AssertExtensions.Throws("path", null, () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); - } /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. + /// This test checks whether or not ZipFile.ExtractToDirectory() is capable of handling filenames + /// which contain invalid path characters in Windows. + /// Archive: InvalidWindowsFileNameChars.zip + /// Test/ + /// Test/normalText.txt + /// Test"<>|^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_/ + /// Test"<>|^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_/TestText1"<>|^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_.txt + /// TestEmpty/ + /// TestText"<>|^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_.txt /// - [Theory] - [InlineData("WindowsInvalid_FromUnix")] - [InlineData("WindowsInvalid_FromWindows")] - [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path - public void Windows_ZipWithInvalidFileNames_ThrowsIOException(string zipName) + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void Windows_ZipWithInvalidFileNames() { - AssertExtensions.Throws(() => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + + var testDirectory = GetTestFilePath(); + ZipFile.ExtractToDirectory(compat("InvalidWindowsFileNameChars.zip"), testDirectory); + CheckExists(testDirectory, "TestText______________________________________.txt"); + CheckExists(testDirectory, "Test______________________________________/TestText1______________________________________.txt"); + CheckExists(testDirectory, "Test/normalText.txt"); + + ZipFile.ExtractToDirectory(compat("NullCharFileName_FromWindows.zip"), testDirectory); + CheckExists(testDirectory, "a_6b6d"); + + ZipFile.ExtractToDirectory(compat("NullCharFileName_FromUnix.zip"), testDirectory); + CheckExists(testDirectory, "a_6b6d"); + + ZipFile.ExtractToDirectory(compat("WindowsInvalid_FromUnix.zip"), testDirectory); + CheckExists(testDirectory, "aa_b_d"); + + ZipFile.ExtractToDirectory(compat("WindowsInvalid_FromWindows.zip"), testDirectory); + CheckExists(testDirectory, "aa_b_d"); + + void CheckExists(string testDirectory, string file) + { + string path = Path.Combine(testDirectory, file); + Assert.True(File.Exists(path)); + File.Delete(path); + } } [Theory]