using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net.Http; using Server.Buffers; namespace Server.Compression { public static class TarArchive { private const string _libArchiveWindowsUrl = @"https://www.libarchive.org/downloads/libarchive-v3.5.2-win64.zip"; private static string _pathToTar; private static string GetPathToTar() { if (!Core.IsWindows || File.Exists(@"C:\Windows\system32\tar.exe")) { return "tar"; } var tarPath = Path.Combine(Core.BaseDirectory, "bsdtar/bsdtar.exe"); return File.Exists(tarPath) ? tarPath : DownloadTarForWindows(); } private static int RunTar(string arguments, string compressionProgramPath) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = _pathToTar, Arguments = arguments } }; if (compressionProgramPath != null) { var envPaths = $"{process.StartInfo.EnvironmentVariables["PATH"]}:{compressionProgramPath}"; process.StartInfo.EnvironmentVariables["PATH"] = envPaths; } process.Start(); process.WaitForExit(); return process.ExitCode; } private static string DownloadTarForWindows() { var tempDir = PathUtility.EnsureRandomPath(Path.GetTempPath()); var libarchiveFile = Path.Combine(tempDir, "libarchive.zip"); // This isn't called often so we don't need to optimize using (HttpClient hc = new HttpClient()) { var result = hc.Send(new HttpRequestMessage(HttpMethod.Get, new Uri(_libArchiveWindowsUrl))); using var stream = result.Content.ReadAsStream(); using FileStream fs = new FileStream(libarchiveFile, FileMode.Create, FileAccess.Write, FileShare.None); stream.CopyTo(fs); } ZipFile.ExtractToDirectory(libarchiveFile, tempDir); var libArchivePath = Path.Combine(tempDir, "libarchive"); PathUtility.MoveDirectory(Path.Combine(libArchivePath, "bin"), Path.Combine(Core.BaseDirectory, "bsdtar")); File.Delete(libarchiveFile); return Path.Combine(Core.BaseDirectory, "bsdtar/bsdtar.exe"); } public static bool ExtractToDirectory( string fileNamePath, string outputDirectory, string compressCommand = null, string compressionProgramPath = null ) { _pathToTar ??= GetPathToTar(); var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : ""; var arguments = $"{useExternalCompression} -xf \"{fileNamePath}\" -C \"{outputDirectory}\""; return RunTar(arguments, compressionProgramPath) == 0; } public static bool CreateFromPaths( IEnumerable paths, string destinationArchiveFileName, string relativeTo, string compressCommand = null, string compressionProgramPath = null ) { _pathToTar ??= GetPathToTar(); new FileInfo(destinationArchiveFileName).EnsureDirectory(); using var builder = ValueStringBuilder.Create(); var i = 0; foreach (var path in paths) { builder.Append($"{(i++ > 0 ? " " : "")}\"{Path.GetRelativePath(relativeTo, path)}\""); } var pathsToCompress = builder.ToString(); var tarFlags = compressCommand == null ? "-acf" : "-cf"; var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : ""; var arguments = $"{useExternalCompression}{tarFlags} \"{destinationArchiveFileName}\" -C \"{relativeTo}\" {pathsToCompress}"; return RunTar(arguments, compressionProgramPath) == 0; } } }