From 192b9c1e8bb36b76fb2b05ebd36918982bb1d6bf Mon Sep 17 00:00:00 2001 From: Mark Final Date: Thu, 1 Nov 2018 21:48:48 +0000 Subject: [PATCH] Ref #248. Ref #132. Tar reader support for symlinks for .NET standard 2 and Posix platforms Extracts linkname from the tar header, and exposes this on IEntry as the LinkTarget (string) property. If an entry is not a symlink, then that property is null. Uses Mono.Posix.NETStandard nuget to create a symlink. However, this is only applicable to .NET standard 2.0+. So far, unable to find a nuget that works for older versions. Also, not sure what to do on Windows. --- src/SharpCompress/Common/Entry.cs | 5 +++ src/SharpCompress/Common/ExtractionMethods.cs | 41 ++++++++++++++----- src/SharpCompress/Common/GZip/GZipEntry.cs | 2 + src/SharpCompress/Common/IEntry.cs | 1 + src/SharpCompress/Common/Rar/RarEntry.cs | 2 + .../Common/SevenZip/SevenZipEntry.cs | 2 + .../Common/Tar/Headers/TarHeader.cs | 7 ++++ src/SharpCompress/Common/Tar/TarEntry.cs | 2 + src/SharpCompress/Common/Zip/ZipEntry.cs | 2 + src/SharpCompress/SharpCompress.csproj | 1 + 10 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/SharpCompress/Common/Entry.cs b/src/SharpCompress/Common/Entry.cs index b5f58055e..c12cdf6f8 100644 --- a/src/SharpCompress/Common/Entry.cs +++ b/src/SharpCompress/Common/Entry.cs @@ -15,6 +15,11 @@ public abstract class Entry : IEntry /// public abstract string Key { get; } + /// + /// The target of a symlink entry internal to the Archive. Will be null if not a symlink. + /// + public abstract string LinkTarget { get; } + /// /// The compressed file size /// diff --git a/src/SharpCompress/Common/ExtractionMethods.cs b/src/SharpCompress/Common/ExtractionMethods.cs index 71e67a976..5d59910cc 100644 --- a/src/SharpCompress/Common/ExtractionMethods.cs +++ b/src/SharpCompress/Common/ExtractionMethods.cs @@ -68,19 +68,40 @@ public static void WriteEntryToFile(IEntry entry, string destinationFileName, ExtractionOptions options, Action openAndWrite) { - FileMode fm = FileMode.Create; - options = options ?? new ExtractionOptions() - { - Overwrite = true - }; - - if (!options.Overwrite) +#if NETSTANDARD2_0 + if (entry.LinkTarget != null) { - fm = FileMode.CreateNew; + /* + var parDir = System.IO.Path.GetDirectoryName(destinationFileName); + if (!System.IO.Directory.Exists(parDir)) + { + System.IO.Directory.CreateDirectory(parDir); + } + */ + var link = new Mono.Unix.UnixSymbolicLinkInfo(destinationFileName); + if (System.IO.File.Exists(destinationFileName)) + { + link.Delete(); // equivalent to ln -s -f + } + link.CreateSymbolicLinkTo(entry.LinkTarget); } + else +#endif + { + FileMode fm = FileMode.Create; + options = options ?? new ExtractionOptions() + { + Overwrite = true + }; - openAndWrite(destinationFileName, fm); - entry.PreserveExtractionOptions(destinationFileName, options); + if (!options.Overwrite) + { + fm = FileMode.CreateNew; + } + + openAndWrite(destinationFileName, fm); + entry.PreserveExtractionOptions(destinationFileName, options); + } } #endif } diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 0b43cd451..8b2d3e937 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -20,6 +20,8 @@ internal GZipEntry(GZipFilePart filePart) public override string Key => _filePart.FilePartName; + public override string LinkTarget => null; + public override long CompressedSize => 0; public override long Size => 0; diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index e8f13a30e..84cfcb33a 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -10,6 +10,7 @@ public interface IEntry long Crc { get; } DateTime? CreatedTime { get; } string Key { get; } + string LinkTarget { get; } bool IsDirectory { get; } bool IsEncrypted { get; } bool IsSplitAfter { get; } diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index 85be0bb32..c461f42a4 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -22,6 +22,8 @@ public abstract class RarEntry : Entry /// public override string Key => FileHeader.FileName; + public override string LinkTarget => null; + /// /// The entry last modified time in the archive, if recorded /// diff --git a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs index 584bfb95d..c2ea2efe8 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs @@ -18,6 +18,8 @@ internal SevenZipEntry(SevenZipFilePart filePart) public override string Key => FilePart.Header.Name; + public override string LinkTarget => null; + public override long CompressedSize => 0; public override long Size => FilePart.Header.Size; diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 7cdfcef3e..db6d5cf4b 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -15,6 +15,7 @@ public TarHeader(ArchiveEncoding archiveEncoding) } internal string Name { get; set; } + internal string LinkName { get; set; } //internal int Mode { get; set; } //internal int UserId { get; set; } @@ -98,6 +99,12 @@ internal bool Read(BinaryReader reader) return false; } + // for symlinks, additionally read the linkname + if (ReadEntryType(buffer) == EntryType.SymLink) + { + LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls(); + } + if (ReadEntryType(buffer) == EntryType.LongName) { Name = ReadLongName(reader, buffer); diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index 82e707d05..6ec5d3199 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -23,6 +23,8 @@ internal TarEntry(TarFilePart filePart, CompressionType type) public override string Key => _filePart.Header.Name; + public override string LinkTarget => _filePart.Header.LinkName; + public override long CompressedSize => _filePart.Header.Size; public override long Size => _filePart.Header.Size; diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index 379534ffa..3f65adaea 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -60,6 +60,8 @@ public override CompressionType CompressionType public override string Key => _filePart.Header.Name; + public override string LinkTarget => null; + public override long CompressedSize => _filePart.Header.CompressedSize; public override long Size => _filePart.Header.UncompressedSize; diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 1e009ded0..66775ecc9 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -34,6 +34,7 @@ $(DefineConstants);NETCORE + \ No newline at end of file