Fix bug compressing directories with deeply nested/recursive symlinks (#197)

This commit is contained in:
Caelan
2024-08-06 23:06:37 +01:00
committed by GitHub
parent 3128d34b14
commit 53de2732d7
2 changed files with 65 additions and 10 deletions

View File

@@ -61,8 +61,10 @@ namespace Velopack.Compression
if (!Utility.IsFileInDirectory(absolute, destinationDirectoryName)) {
throw new IOException("IO_SymlinkTargetNotInDirectory");
}
SymbolicLink.Create(fileDestinationPath, absolute, true, true);
}
return;
}
@@ -94,6 +96,7 @@ namespace Velopack.Compression
await DeterministicCreateFromDirectoryAsync(directoryToCompress, outputFile, compressionLevel, progress, cancelToken).ConfigureAwait(false);
} catch {
try { File.Delete(outputFile); } catch { }
throw;
}
}
@@ -101,7 +104,8 @@ namespace Velopack.Compression
private static char s_pathSeperator = '/';
public static readonly DateTime ZipFormatMinDate = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static async Task DeterministicCreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel,
private static async Task DeterministicCreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName,
CompressionLevel compressionLevel,
Action<int> progress, CancellationToken cancelToken)
{
Encoding entryNameEncoding = Encoding.UTF8;
@@ -114,20 +118,19 @@ namespace Velopack.Compression
long totalBytes = directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories).Sum(f => f.Length);
long processedBytes = 0L;
var directories = directoryInfo
.EnumerateDirectories("*", SearchOption.AllDirectories)
.Concat(new[] { directoryInfo })
.OrderBy(f => f.FullName)
.ToArray();
var dirsToProcess = new Stack<DirectoryInfo>();
dirsToProcess.Push(directoryInfo);
foreach (var dir in directories) {
while (dirsToProcess.Count > 0) {
cancelToken.ThrowIfCancellationRequested();
var dir = dirsToProcess.Pop();
// if dir is a symlink, write it as a file containing path to target
if (SymbolicLink.Exists(dir.FullName)) {
if (!Utility.IsFileInDirectory(SymbolicLink.GetTarget(dir.FullName, relative: false), sourceDirectoryName)) {
throw new IOException("IO_SymlinkTargetNotInDirectory");
}
string entryName = EntryFromPath(dir.FullName, fullName.Length, dir.FullName.Length - fullName.Length);
string symlinkTarget = SymbolicLink.GetTarget(dir.FullName, relative: true)
.Replace(Path.DirectorySeparatorChar, s_pathSeperator) + s_pathSeperator;
@@ -135,6 +138,7 @@ namespace Velopack.Compression
using (var writer = new StreamWriter(entry.Open())) {
await writer.WriteAsync(symlinkTarget).ConfigureAwait(false);
}
continue;
}
@@ -146,7 +150,12 @@ namespace Velopack.Compression
continue;
}
// if none of the above, enumerate files and add them to the archive
// if none of the above, this is just a regular folder - so we'll enumerate dirs and add them to the search stack and
// enumerate files and add them to the archive
foreach (var subdir in dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) {
dirsToProcess.Push(subdir);
}
var files = dir
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.OrderBy(f => f.FullName)
@@ -163,12 +172,14 @@ namespace Velopack.Compression
if (!Utility.IsFileInDirectory(SymbolicLink.GetTarget(fileInfo.FullName, relative: false), sourceDirectoryName)) {
throw new IOException("IO_SymlinkTargetNotInDirectory");
}
string symlinkTarget = SymbolicLink.GetTarget(fileInfo.FullName, relative: true)
.Replace(Path.DirectorySeparatorChar, s_pathSeperator);
var entry = zipArchive.CreateEntry(entryName + SYMLINK_EXT);
using (var writer = new StreamWriter(entry.Open())) {
await writer.WriteAsync(symlinkTarget).ConfigureAwait(false);
}
continue;
}

View File

@@ -1,4 +1,7 @@
namespace Velopack.Tests;
using System.IO.Compression;
using Velopack.Compression;
namespace Velopack.Tests;
public class SymbolicLinkTests
{
@@ -220,4 +223,45 @@ public class SymbolicLinkTests
Assert.Throws<IOException>(() => SymbolicLink.Delete(Path.Combine(tempFolder, "AFile")));
}
[Fact]
public async Task ComplexSymlinkDirGetsZippedCorrectly()
{
using var _1 = Utility.GetTempDirectory(out var tempFolder);
var temp = new DirectoryInfo(tempFolder);
var versions = temp.CreateSubdirectory("Versions");
var a = versions.CreateSubdirectory("A");
var resources = a.CreateSubdirectory("Resources");
File.WriteAllText(Path.Combine(resources.FullName, "Info.plist"), "Hello, Resources!");
File.WriteAllText(Path.Combine(a.FullName, "App"), "Hello, App!");
SymbolicLink.Create(Path.Combine(versions.FullName, "Current"), a.FullName, false, true);
SymbolicLink.Create(Path.Combine(temp.FullName, "Resources"), Path.Combine(versions.FullName, "Current", "Resources"), false, true);
SymbolicLink.Create(Path.Combine(temp.FullName, "App"), Path.Combine(versions.FullName, "Current", "App"), false, true);
using var _2 = Utility.GetTempDirectory(out var tempOutput);
var output = Path.Combine(tempOutput, "output.zip");
await EasyZip.CreateZipFromDirectoryAsync(NullLogger.Instance, output, tempFolder);
ZipFile.ExtractToDirectory(output, tempOutput);
var appSym = Path.Combine(tempOutput, "App.__symlink");
Assert.True(File.Exists(appSym));
Assert.Equal("Versions/Current/App", File.ReadAllText(appSym));
var resSym = Path.Combine(tempOutput, "Resources.__symlink");
Assert.True(File.Exists(resSym));
Assert.Equal("Versions/Current/Resources/", File.ReadAllText(resSym));
Assert.True(Directory.Exists(Path.Combine(tempOutput, "Versions")));
Assert.False(Directory.Exists(Path.Combine(tempOutput, "App")));
Assert.False(Directory.Exists(Path.Combine(tempOutput, "Resources")));
Assert.True(Directory.Exists(Path.Combine(tempOutput, "Versions", "A")));
Assert.False(Directory.Exists(Path.Combine(tempOutput, "Versions", "Current")));
Assert.False(File.Exists(Path.Combine(tempOutput, "Versions", "Current")));
var currentSym = Path.Combine(tempOutput, "Versions", "Current.__symlink");
Assert.True(File.Exists(currentSym));
Assert.Equal("A/", File.ReadAllText(currentSym));
}
}