Skip to content

Commit

Permalink
[release/7.0] Fix relative symlink support in TarFile (#78470)
Browse files Browse the repository at this point in the history
* 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: Adeel <3840695+am11@users.noreply.github.com>
Co-authored-by: David Cantú <dacantu@microsoft.com>
  • Loading branch information
3 people committed Nov 29, 2022
1 parent ba5c173 commit e4b6123
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,17 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
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(string.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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,29 @@ public async Task ExtractEntry_DockerImageTarWithFileTypeInDirectoriesInMode_Suc
}
}

[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)]
Expand Down

0 comments on commit e4b6123

Please sign in to comment.