diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs b/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs index 6db6b23b9..878649017 100644 --- a/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs +++ b/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs @@ -356,8 +356,7 @@ public string RootPath { throw new ObjectDisposedException("TarArchive"); } - // Convert to forward slashes for matching. Trim trailing / for correct final path - rootPath = value.Replace('\\', '/').TrimEnd('/'); + rootPath = value.ToTarArchivePath().TrimEnd('/'); } } @@ -660,7 +659,9 @@ private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraver string destFile = Path.Combine(destDir, name); var destFileDir = Path.GetDirectoryName(Path.GetFullPath(destFile)) ?? ""; - if (!allowParentTraversal && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase)) + var isRootDir = entry.IsDirectory && entry.Name == ""; + + if (!allowParentTraversal && !isRootDir && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase)) { throw new InvalidNameException("Parent traversal in paths is not allowed"); } diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs b/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs index 2f3cf7862..82c813367 100644 --- a/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs +++ b/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs @@ -372,15 +372,10 @@ public void GetFileTarHeader(TarHeader header, string file) } */ - name = name.Replace(Path.DirectorySeparatorChar, '/'); - // No absolute pathnames // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\", // so we loop on starting /'s. - while (name.StartsWith("/", StringComparison.Ordinal)) - { - name = name.Substring(1); - } + name = name.ToTarArchivePath(); header.LinkName = String.Empty; header.Name = name; diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs b/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs new file mode 100644 index 000000000..433c6a424 --- /dev/null +++ b/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs @@ -0,0 +1,13 @@ +using System.IO; +using ICSharpCode.SharpZipLib.Core; + +namespace ICSharpCode.SharpZipLib.Tar +{ + internal static class TarStringExtension + { + public static string ToTarArchivePath(this string s) + { + return PathUtils.DropPathRoot(s).Replace(Path.DirectorySeparatorChar, '/'); + } + } +} diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs index d1f1f89bc..c6a35ff08 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs @@ -833,7 +833,7 @@ public void ParseHeaderWithEncoding(int length, string encodingName) reparseHeader.ParseBuffer(headerbytes, enc); Assert.AreEqual(name, reparseHeader.Name); // top 100 bytes are name field in tar header - for (int i = 0;i < encodedName.Length;i++) + for (int i = 0; i < encodedName.Length; i++) { Assert.AreEqual(encodedName[i], headerbytes[i]); } @@ -852,9 +852,9 @@ public async Task StreamWithJapaneseNameAsync(int length, string encodingName) var entryName = new string((char)0x3042, length); var data = new byte[32]; var encoding = Encoding.GetEncoding(encodingName); - using(var memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - using(var tarOutput = new TarOutputStream(memoryStream, encoding)) + using (var tarOutput = new TarOutputStream(memoryStream, encoding)) { var entry = TarEntry.CreateTarEntry(entryName); entry.Size = 32; @@ -874,5 +874,47 @@ public async Task StreamWithJapaneseNameAsync(int length, string encodingName) File.WriteAllBytes(Path.Combine(Path.GetTempPath(), $"jpnametest_{length}_{encodingName}.tar"), memoryStream.ToArray()); } } + /// + /// This test could be considered integration test. it creates a tar archive with the root directory specified + /// Then extracts it and compares the two folders. This used to fail on unix due to issues with root folder handling + /// in the tar archive. + /// + [Test] + [Category("Tar")] + public void RootPathIsRespected() + { + using (var extractDirectory = new TempDir()) + using (var tarFileName = new TempFile()) + using (var tempDirectory = new TempDir()) + { + tempDirectory.CreateDummyFile(); + + using (var tarFile = File.Open(tarFileName.FullName, FileMode.Create)) + { + using (var tarOutputStream = TarArchive.CreateOutputTarArchive(tarFile)) + { + tarOutputStream.RootPath = tempDirectory.FullName; + var entry = TarEntry.CreateEntryFromFile(tempDirectory.FullName); + tarOutputStream.WriteEntry(entry, true); + } + } + + using (var file = File.OpenRead(tarFileName.FullName)) + { + using (var archive = TarArchive.CreateInputTarArchive(file, Encoding.UTF8)) + { + archive.ExtractContents(extractDirectory.FullName); + } + } + + var expectationDirectory = new DirectoryInfo(tempDirectory.FullName); + foreach (var checkFile in expectationDirectory.GetFiles("", SearchOption.AllDirectories)) + { + var relativePath = checkFile.FullName.Substring(expectationDirectory.FullName.Length + 1); + FileAssert.Exists(Path.Combine(extractDirectory.FullName, relativePath)); + FileAssert.AreEqual(checkFile.FullName, Path.Combine(extractDirectory.FullName, relativePath)); + } + } + } } }