diff --git a/src/SharpCompress/Common/Entry.cs b/src/SharpCompress/Common/Entry.cs
index b5f58055..c12cdf6f 100644
--- a/src/SharpCompress/Common/Entry.cs
+++ b/src/SharpCompress/Common/Entry.cs
@@ -15,6 +15,11 @@ namespace SharpCompress.Common
///
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 71e67a97..15efd220 100644
--- a/src/SharpCompress/Common/ExtractionMethods.cs
+++ b/src/SharpCompress/Common/ExtractionMethods.cs
@@ -68,19 +68,30 @@ namespace SharpCompress.Common
ExtractionOptions options,
Action openAndWrite)
{
- FileMode fm = FileMode.Create;
- options = options ?? new ExtractionOptions()
- {
- Overwrite = true
- };
-
- if (!options.Overwrite)
+ if (entry.LinkTarget != null)
{
- fm = FileMode.CreateNew;
+ if (null == options.WriteSymbolicLink)
+ {
+ throw new ExtractionException("Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null");
+ }
+ options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
}
+ else
+ {
+ 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/ExtractionOptions.cs b/src/SharpCompress/Common/ExtractionOptions.cs
index 9dba482e..7f6e1efc 100644
--- a/src/SharpCompress/Common/ExtractionOptions.cs
+++ b/src/SharpCompress/Common/ExtractionOptions.cs
@@ -21,5 +21,14 @@
/// preserve windows file attributes
///
public bool PreserveAttributes { get; set; }
+
+ ///
+ /// Delegate for writing symbolic links to disk.
+ /// sourcePath is where the symlink is created.
+ /// targetPath is what the symlink refers to.
+ ///
+ public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath);
+
+ public SymbolicLinkWriterDelegate WriteSymbolicLink;
}
}
\ No newline at end of file
diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs
index 0b43cd45..8b2d3e93 100644
--- a/src/SharpCompress/Common/GZip/GZipEntry.cs
+++ b/src/SharpCompress/Common/GZip/GZipEntry.cs
@@ -20,6 +20,8 @@ namespace SharpCompress.Common.GZip
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 e8f13a30..84cfcb33 100644
--- a/src/SharpCompress/Common/IEntry.cs
+++ b/src/SharpCompress/Common/IEntry.cs
@@ -10,6 +10,7 @@ namespace SharpCompress.Common
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 85be0bb3..c461f42a 100644
--- a/src/SharpCompress/Common/Rar/RarEntry.cs
+++ b/src/SharpCompress/Common/Rar/RarEntry.cs
@@ -22,6 +22,8 @@ namespace SharpCompress.Common.Rar
///
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 584bfb95..c2ea2efe 100644
--- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs
+++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs
@@ -18,6 +18,8 @@ namespace SharpCompress.Common.SevenZip
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 7cdfcef3..db6d5cf4 100644
--- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs
+++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs
@@ -15,6 +15,7 @@ namespace SharpCompress.Common.Tar.Headers
}
internal string Name { get; set; }
+ internal string LinkName { get; set; }
//internal int Mode { get; set; }
//internal int UserId { get; set; }
@@ -98,6 +99,12 @@ namespace SharpCompress.Common.Tar.Headers
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 82e707d0..6ec5d319 100644
--- a/src/SharpCompress/Common/Tar/TarEntry.cs
+++ b/src/SharpCompress/Common/Tar/TarEntry.cs
@@ -23,6 +23,8 @@ namespace SharpCompress.Common.Tar
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 379534ff..3f65adae 100644
--- a/src/SharpCompress/Common/Zip/ZipEntry.cs
+++ b/src/SharpCompress/Common/Zip/ZipEntry.cs
@@ -60,6 +60,8 @@ namespace SharpCompress.Common.Zip
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/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj
index 6223a62d..4d08ac18 100644
--- a/tests/SharpCompress.Test/SharpCompress.Test.csproj
+++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj
@@ -16,5 +16,6 @@
+
\ No newline at end of file
diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs
index 10c7ee19..a5b75e4a 100644
--- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs
+++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs
@@ -186,5 +186,65 @@ namespace SharpCompress.Test.Tar
}
}
}
+
+ [Fact]
+ public void Tar_GZip_With_Symlink_Entries()
+ {
+ var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
+ System.Runtime.InteropServices.OSPlatform.Windows);
+ using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz")))
+ using (var reader = TarReader.Open(stream))
+ {
+ List names = new List();
+ while (reader.MoveToNextEntry())
+ {
+ if (reader.Entry.IsDirectory)
+ {
+ continue;
+ }
+ reader.WriteEntryToDirectory(SCRATCH_FILES_PATH,
+ new ExtractionOptions()
+ {
+ ExtractFullPath = true,
+ Overwrite = true,
+ WriteSymbolicLink = (sourcePath, targetPath) =>
+ {
+ if (!isWindows)
+ {
+ var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath);
+ if (System.IO.File.Exists(sourcePath))
+ {
+ link.Delete(); // equivalent to ln -s -f
+ }
+ link.CreateSymbolicLinkTo(targetPath);
+ }
+ }
+ });
+ if (!isWindows)
+ {
+ if (reader.Entry.LinkTarget != null)
+ {
+ var path = System.IO.Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key);
+ var link = new Mono.Unix.UnixSymbolicLinkInfo(path);
+ if (link.HasContents)
+ {
+ // need to convert the link to an absolute path for comparison
+ var target = reader.Entry.LinkTarget;
+ var realTarget = System.IO.Path.GetFullPath(
+ System.IO.Path.Combine($"{System.IO.Path.GetDirectoryName(path)}",
+ target)
+ );
+
+ Assert.Equal(realTarget, link.GetContents().ToString());
+ }
+ else
+ {
+ Assert.True(false, "Symlink has no target");
+ }
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/tests/TestArchives/Archives/TarWithSymlink.tar.gz b/tests/TestArchives/Archives/TarWithSymlink.tar.gz
new file mode 100644
index 00000000..4dce43df
Binary files /dev/null and b/tests/TestArchives/Archives/TarWithSymlink.tar.gz differ