diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d629df7063f7b4152bf091809d1bf4b08e08fc15..92accd2d9647bd5eef6f1305f7c095e7ac4b01a4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -138,9 +138,9 @@ https://github.com/dotnet/runtime-assets 0920468fa7db4ee8ea8bbcba186421cb92713adf - + https://github.com/dotnet/runtime-assets - 0920468fa7db4ee8ea8bbcba186421cb92713adf + 371af1f99788b76eae14b96aad4ab7ac9b373938 https://github.com/dotnet/runtime-assets diff --git a/eng/Versions.props b/eng/Versions.props index c9cd5414c496b9eabf0ff8b8fb4b24f0859df436..479b25b159147b6c2bb80824cd87344479c5f879 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -120,7 +120,7 @@ 7.0.0-beta.22281.1 7.0.0-beta.22281.1 7.0.0-beta.22281.1 - 7.0.0-beta.22281.1 + 7.0.0-beta.22313.1 7.0.0-beta.22281.1 7.0.0-beta.22281.1 7.0.0-beta.22281.1 diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index f2c5481509817b920939296f2973ecbac029f645..470a097e526f805e1e41fce9a885eed3334b2c5f 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -238,6 +238,7 @@ private static bool GetAlpnSupport() public static bool SupportsAlpn => s_supportsAlpn.Value; public static bool SupportsClientAlpn => SupportsAlpn || IsOSX || IsMacCatalyst || IsiOS || IstvOS; + public static bool SupportsHardLinkCreation => !IsAndroid; private static readonly Lazy s_supportsTls10 = new Lazy(GetTls10Support); private static readonly Lazy s_supportsTls11 = new Lazy(GetTls11Support); diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs index a2328ea9691a3ca3b6946d56d984f889441e30c3..ca1e482cb826397b829b3fbd8532345e95a12e49 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs @@ -261,9 +261,13 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o Debug.Assert(!string.IsNullOrEmpty(destinationDirectoryPath)); Debug.Assert(Path.IsPathFullyQualified(destinationDirectoryPath)); - string destinationDirectoryFullPath = destinationDirectoryPath.EndsWith(Path.DirectorySeparatorChar) ? destinationDirectoryPath : destinationDirectoryPath + Path.DirectorySeparatorChar; + destinationDirectoryPath = Path.TrimEndingDirectorySeparator(destinationDirectoryPath); - string fileDestinationPath = GetSanitizedFullPath(destinationDirectoryFullPath, Name, SR.TarExtractingResultsFileOutside); + string? fileDestinationPath = GetSanitizedFullPath(destinationDirectoryPath, Name); + if (fileDestinationPath == null) + { + throw new IOException(string.Format(SR.TarExtractingResultsFileOutside, Name, destinationDirectoryPath)); + } string? linkTargetPath = null; if (EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) @@ -273,7 +277,11 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o throw new FormatException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } - linkTargetPath = GetSanitizedFullPath(destinationDirectoryFullPath, LinkName, SR.TarExtractingResultsLinkOutside); + linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, LinkName); + if (linkTargetPath == null) + { + throw new IOException(string.Format(SR.TarExtractingResultsLinkOutside, LinkName, destinationDirectoryPath)); + } } if (EntryType == TarEntryType.Directory) @@ -286,26 +294,15 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!); ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite); } + } - // If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, throws. - static string GetSanitizedFullPath(string destinationDirectoryFullPath, string path, string exceptionMessage) - { - string actualPath = Path.Join(Path.GetDirectoryName(path), ArchivingUtils.SanitizeEntryFilePath(Path.GetFileName(path))); - - if (!Path.IsPathFullyQualified(actualPath)) - { - actualPath = Path.Combine(destinationDirectoryFullPath, actualPath); - } - - actualPath = Path.GetFullPath(actualPath); - - if (!actualPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison)) - { - throw new IOException(string.Format(exceptionMessage, path, destinationDirectoryFullPath)); - } - - return actualPath; - } + // If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, returns null. + private static string? GetSanitizedFullPath(string destinationDirectoryFullPath, string path) + { + string fullyQualifiedPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(destinationDirectoryFullPath, path); + string normalizedPath = Path.GetFullPath(fullyQualifiedPath); // Removes relative segments + string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(Path.GetFileName(normalizedPath))); + return sanitizedPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison) ? sanitizedPath : null; } // Extracts the current entry into the filesystem, regardless of the entry type. diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index 3b53355dd6b46b1a688c237d3d87737bfc842a87..95cbdd4e41e811063dfd949cf5de587f16bae322 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -17,6 +17,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs index 70453f4d49931ee8fa66bbc2becd2eeef57248d7..a753358cb8a3940db124a9b5fd8219366725b458 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs @@ -136,5 +136,28 @@ public void Extract_AllSegmentsOfPath() string filePath = Path.Join(segment2Path, "file.txt"); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); } + + [Fact] + public void ExtractArchiveWithEntriesThatStartWithSlashDotPrefix() + { + using TempDirectory root = new TempDirectory(); + + using MemoryStream archiveStream = GetStrangeTarMemoryStream("prefixDotSlashAndCurrentFolderEntry"); + + TarFile.ExtractToDirectory(archiveStream, root.Path, overwriteFiles: true); + + archiveStream.Position = 0; + + using TarReader reader = new TarReader(archiveStream, leaveOpen: false); + + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + // Normalize the path (remove redundant segments), remove trailing separators + // this is so the first entry can be skipped if it's the same as the root directory + string entryPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(Path.Join(root.Path, entry.Name))); + Assert.True(Path.Exists(entryPath), $"Entry was not extracted: {entryPath}"); + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index 8b9af8b3bc3e26477e8b2877e9ab2f9e848a312e..9464e92488795e0891eb1d10bfb834b7ecd31ba3 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -97,8 +97,7 @@ public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType) [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] public void Extract_SymbolicLinkEntry_TargetInsideDirectory() => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.SymbolicLink); - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/68360", TestPlatforms.Android | TestPlatforms.LinuxBionic | TestPlatforms.iOS | TestPlatforms.tvOS)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))] public void Extract_HardLinkEntry_TargetInsideDirectory() => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.HardLink); private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entryType) diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.Unix.cs new file mode 100644 index 0000000000000000000000000000000000000000..a4333bb21792854842a8bd93add4feaa71627395 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.Unix.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public partial class TarReader_ExtractToFile_Tests : TarTestsBase + { + [Fact] + public void ExtractToFile_SpecialFile_Unelevated_Throws() + { + using TempDirectory root = new TempDirectory(); + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.ustar, "specialfiles"); + + using TarReader reader = new TarReader(ms); + + string path = Path.Join(root.Path, "output"); + + // Block device requires elevation for writing + PosixTarEntry blockDevice = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(blockDevice); + Assert.Throws(() => blockDevice.ExtractToFile(path, overwrite: false)); + Assert.False(File.Exists(path)); + + // Character device requires elevation for writing + PosixTarEntry characterDevice = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(characterDevice); + Assert.Throws(() => characterDevice.ExtractToFile(path, overwrite: false)); + Assert.False(File.Exists(path)); + + // Fifo does not require elevation, should succeed + PosixTarEntry fifo = reader.GetNextEntry() as PosixTarEntry; + Assert.NotNull(fifo); + fifo.ExtractToFile(path, overwrite: false); + Assert.True(File.Exists(path)); + + Assert.Null(reader.GetNextEntry()); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.cs index bf5b6a110b570cfe356570b91f2203766f915f49..c347c5e4fdc3d001a29b4457b358c3a6da2cc171 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.ExtractToFile.Tests.cs @@ -1,44 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.IO; -using System.Linq; using Xunit; namespace System.Formats.Tar.Tests { - public class TarReader_ExtractToFile_Tests : TarTestsBase + public partial class TarReader_ExtractToFile_Tests : TarTestsBase { [Fact] - public void ExtractToFile_SpecialFile_Unelevated_Throws() + public void ExtractEntriesWithSlashDotPrefix() { using TempDirectory root = new TempDirectory(); - using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.ustar, "specialfiles"); - using TarReader reader = new TarReader(ms); - - string path = Path.Join(root.Path, "output"); - - // Block device requires elevation for writing - PosixTarEntry blockDevice = reader.GetNextEntry() as PosixTarEntry; - Assert.NotNull(blockDevice); - Assert.Throws(() => blockDevice.ExtractToFile(path, overwrite: false)); - Assert.False(File.Exists(path)); - - // Character device requires elevation for writing - PosixTarEntry characterDevice = reader.GetNextEntry() as PosixTarEntry; - Assert.NotNull(characterDevice); - Assert.Throws(() => characterDevice.ExtractToFile(path, overwrite: false)); - Assert.False(File.Exists(path)); - - // Fifo does not require elevation, should succeed - PosixTarEntry fifo = reader.GetNextEntry() as PosixTarEntry; - Assert.NotNull(fifo); - fifo.ExtractToFile(path, overwrite: false); - Assert.True(File.Exists(path)); - - Assert.Null(reader.GetNextEntry()); + using MemoryStream archiveStream = GetStrangeTarMemoryStream("prefixDotSlashAndCurrentFolderEntry"); + using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) + { + string rootPath = Path.TrimEndingDirectorySeparator(root.Path); + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + Assert.NotNull(entry); + Assert.StartsWith("./", entry.Name); + // Normalize the path (remove redundant segments), remove trailing separators + // this is so the first entry can be skipped if it's the same as the root directory + string entryPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(Path.Join(rootPath, entry.Name))); + if (entryPath != rootPath) + { + entry.ExtractToFile(entryPath, overwrite: true); + Assert.True(Path.Exists(entryPath), $"Entry was not extracted: {entryPath}"); + } + } + } } + } } \ No newline at end of file diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs index fb0467593d18e18121a0c635825f27f921b9af3f..a4c19ddb78402eb1a0c57e2159eb4de1811030a0 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs @@ -96,9 +96,17 @@ protected static string GetTarFilePath(CompressionMethod compressionMethod, Test } // MemoryStream containing the copied contents of the specified file. Meant for reading and writing. - protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) + protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) => + GetMemoryStream(GetTarFilePath(compressionMethod, format, testCaseName)); + + protected static string GetStrangeTarFilePath(string testCaseName) => + Path.Join(Directory.GetCurrentDirectory(), "strange", testCaseName + ".tar"); + + protected static MemoryStream GetStrangeTarMemoryStream(string testCaseName) => + GetMemoryStream(GetStrangeTarFilePath(testCaseName)); + + private static MemoryStream GetMemoryStream(string path) { - string path = GetTarFilePath(compressionMethod, format, testCaseName); MemoryStream ms = new(); using (FileStream fs = File.OpenRead(path)) {