diff options
author | Adeel Mujahid <3840695+am11@users.noreply.github.com> | 2022-11-12 00:46:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-12 00:46:06 +0300 |
commit | 493574ee470daf8a353203ce7e933f931f986159 (patch) | |
tree | 93b6885bdf339e37f4fda0e63c8459f68a80a89c | |
parent | 486682a719e064ed30dd4c6db94f5c34a05ad5f2 (diff) |
Fix relative symlink support in TarFile (#77338)
* Fix relative symlink support in TarFile
* Update src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs
Co-authored-by: David Cantú <dacantu@microsoft.com>
Co-authored-by: David Cantú <dacantu@microsoft.com>
4 files changed, 111 insertions, 2 deletions
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 61368173b0d..e7d6761c592 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 @@ -349,11 +349,17 @@ namespace System.Formats.Tar throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } - linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, LinkName); + linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, + Path.IsPathFullyQualified(LinkName) ? LinkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), LinkName)); + if (linkTargetPath == null) { throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, LinkName, destinationDirectoryPath)); } + + // after TarExtractingResultsLinkOutside validation, preserve the original + // symlink target path (to match behavior of other utilities). + linkTargetPath = LinkName; } return (fileDestinationPath, linkTargetPath); 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 ca1b4d99e50..1d30aa09d0f 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 @@ -2,7 +2,7 @@ <PropertyGroup> <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix</TargetFrameworks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <StringResourcesPath>$(LibrariesProjectRoot)/Common/tests/Resources/Strings.resx</StringResourcesPath> + <StringResourcesPath>$(MSBuildProjectDirectory)\..\src\Resources\Strings.resx</StringResourcesPath> <EnableLibraryImportGenerator>true</EnableLibraryImportGenerator> <IncludeRemoteExecutor>true</IncludeRemoteExecutor> </PropertyGroup> @@ -17,6 +17,7 @@ <Compile Include="TarFile\TarFile.ExtractToDirectoryAsync.File.Tests.cs" /> <Compile Include="TarFile\TarFile.CreateFromDirectoryAsync.Stream.Tests.cs" /> <Compile Include="TarFile\TarFile.CreateFromDirectoryAsync.File.Tests.cs" /> + <Compile Include="TarFile\TarFile.CreateFromDirectory.File.Roundtrip.cs" /> <Compile Include="TarEntry\TarEntry.Conversion.Tests.Base.cs" /> <Compile Include="TarEntry\GnuTarEntry.Conversion.Tests.cs" /> <Compile Include="TarEntry\PaxTarEntry.Conversion.Tests.cs" /> diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs new file mode 100644 index 00000000000..460dec75780 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Roundtrip.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarFile_CreateFromDirectory_Roundtrip_Tests : TarTestsBase + { + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("./file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory")] + [InlineData("../file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", "subDirectory1/subDirectory1.1")] + [InlineData("./file.txt", null)] + public void SymlinkRelativeTargets_InsideTheArchive_RoundtripsSuccessfully(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if(subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.Create(Path.Join(sourceDirectoryName, subDirectory, symlinkTargetPath)).Dispose(); + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + TarFile.ExtractToDirectory(archiveStream, destinationDirectoryName, overwriteFiles: true); + + string destinationSubDirectory = Path.Join(destinationDirectoryName, subDirectory); + string symlinkPath = Path.Join(destinationSubDirectory, "linkToFile"); + Assert.True(File.Exists(symlinkPath)); + + FileInfo? fileInfo = new(symlinkPath); + Assert.Equal(symlinkTargetPath, fileInfo.LinkTarget); + + FileSystemInfo? symlinkTarget = File.ResolveLinkTarget(symlinkPath, returnFinalTarget: true); + Assert.True(File.Exists(symlinkTarget.FullName)); + } + + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + [InlineData("../file.txt", null)] + [InlineData("../../file.txt", "subDirectory")] + public void SymlinkRelativeTargets_OutsideTheArchive_Fails(string symlinkTargetPath, string subDirectory) + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string destinationDirectoryName = Path.Join(root.Path, "destinationDirectory"); + Directory.CreateDirectory(destinationDirectoryName); + + string sourceSubDirectory = Path.Join(sourceDirectoryName, subDirectory); + if(subDirectory != null) Directory.CreateDirectory(sourceSubDirectory); + + File.CreateSymbolicLink(Path.Join(sourceSubDirectory, "linkToFile"), symlinkTargetPath); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + Exception exception = Assert.Throws<IOException>(() => TarFile.ExtractToDirectory(archiveStream, destinationDirectoryName, overwriteFiles: true)); + + Assert.Equal(SR.Format(SR.TarExtractingResultsLinkOutside, symlinkTargetPath, destinationDirectoryName), exception.Message); + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs index ec32ab1ab81..cde32d3f979 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs @@ -110,6 +110,29 @@ namespace System.Formats.Tar.Tests } } + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task ExtractEntry_PodmanImageTarWithRelativeSymlinksPointingInExtractDirectory_SuccessfullyExtracts_Async() + { + using (TempDirectory root = new TempDirectory()) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "misc", "podman-hello-world"); + await TarFile.ExtractToDirectoryAsync(archiveStream, root.Path, overwriteFiles: true); + + Assert.True(File.Exists(Path.Join(root.Path, "manifest.json"))); + Assert.True(File.Exists(Path.Join(root.Path, "repositories"))); + Assert.True(File.Exists(Path.Join(root.Path, "efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507.tar"))); + + string symlinkPath = Path.Join(root.Path, "e7fc2b397c1ab5af9938f18cc9a80d526cccd1910e4678390157d8cc6c94410d/layer.tar"); + Assert.True(File.Exists(symlinkPath)); + + FileInfo? fileInfo = new(symlinkPath); + Assert.Equal("../efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507.tar", fileInfo.LinkTarget); + + FileSystemInfo? symlinkTarget = File.ResolveLinkTarget(symlinkPath, returnFinalTarget: true); + Assert.True(File.Exists(symlinkTarget.FullName)); + } + } + [Theory] [InlineData(TarEntryType.SymbolicLink)] [InlineData(TarEntryType.HardLink)] |