using System; using System.IO; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Semmle.Util.Logging; namespace Semmle.Util { public static class FileUtils { public const string NugetExeUrl = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"; public static readonly char[] NewLineCharacters = ['\r', '\n']; public static string ConvertToWindows(string path) { return path.Replace('/', '\\'); } public static string ConvertToUnix(string path) { return path.Replace('\\', '/'); } public static string ConvertToNative(string path) { return Path.DirectorySeparatorChar == '/' ? path.Replace('\\', '/') : path.Replace('/', '\\'); } /// /// Moves the source file to the destination, overwriting the destination file if /// it exists already. /// /// Source file. /// Target file. public static void MoveOrReplace(string src, string dest) { File.Move(src, dest, overwrite: true); } /// /// Attempt to delete the given file (ignoring I/O exceptions). /// /// The file to delete. public static void TryDelete(string file) { try { File.Delete(file); } catch (IOException) { // Ignore } } /// /// Finds the path for the program based on the /// PATH environment variable, and in the case of Windows the /// PATHEXT environment variable. /// /// Returns null of no path can be found. /// public static string? FindProgramOnPath(string prog) { var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator); string[] exes; if (Win32.IsWindows()) { var extensions = Environment.GetEnvironmentVariable("PATHEXT")?.Split(';')?.ToArray(); exes = extensions is null || extensions.Any(prog.EndsWith) ? new[] { prog } : extensions.Select(ext => prog + ext).ToArray(); } else { exes = new[] { prog }; } var candidates = paths?.Where(path => exes.Any(exe0 => File.Exists(Path.Combine(path, exe0)))); return candidates?.FirstOrDefault(); } /// /// Computes the hash of the file at . /// public static string ComputeFileHash(string filePath) { using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var sha = SHA256.HashData(fileStream); return GetHashString(sha); } /// /// Computes the hash of . /// public static string ComputeHash(string input) { var bytes = Encoding.Unicode.GetBytes(input); var sha = MD5.HashData(bytes); // MD5 to keep it shorter than SHA256 return GetHashString(sha).ToUpper(); } private static string GetHashString(byte[] sha) { var hex = new StringBuilder(sha.Length * 2); foreach (var b in sha) { hex.AppendFormat("{0:x2}", b); } return hex.ToString(); } private static async Task DownloadFileAsync(string address, string filename) { using var httpClient = new HttpClient(); using var contentStream = await httpClient.GetStreamAsync(address); using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); await contentStream.CopyToAsync(stream); } /// /// Downloads the file at to . /// public static void DownloadFile(string address, string fileName) => DownloadFileAsync(address, fileName).GetAwaiter().GetResult(); public static string ConvertPathToSafeRelativePath(string path) { // Remove all leading path separators / or \ // For example, UNC paths have two leading \\ path = path.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (path.Length > 1 && path[1] == ':') path = $"{path[0]}_{path[2..]}"; return path; } public static string NestPaths(ILogger logger, string? outerpath, string innerpath) { var nested = innerpath; if (!string.IsNullOrEmpty(outerpath)) { innerpath = ConvertPathToSafeRelativePath(innerpath); nested = Path.Combine(outerpath, innerpath); } try { var directoryName = Path.GetDirectoryName(nested); if (directoryName is null) { logger.LogWarning($"Failed to get directory name from path '{nested}'."); throw new InvalidOperationException(); } Directory.CreateDirectory(directoryName); } catch (PathTooLongException) { logger.LogWarning($"Failed to create parent directory of '{nested}': Path too long."); throw; } return nested; } private static readonly Lazy tempFolderPath = new Lazy(() => { var tempPath = Path.GetTempPath(); var name = Guid.NewGuid().ToString("N").ToUpper(); var tempFolder = Path.Combine(tempPath, "GitHub", name); Directory.CreateDirectory(tempFolder); return tempFolder; }); public static string GetTemporaryWorkingDirectory(Func getEnvironmentVariable, string lang, out bool shouldCleanUp) { var tempFolder = getEnvironmentVariable($"CODEQL_EXTRACTOR_{lang}_SCRATCH_DIR"); if (!string.IsNullOrEmpty(tempFolder)) { shouldCleanUp = false; return tempFolder; } shouldCleanUp = true; return tempFolderPath.Value; } public static string GetTemporaryWorkingDirectory(out bool shouldCleanUp) => GetTemporaryWorkingDirectory(Environment.GetEnvironmentVariable, "CSHARP", out shouldCleanUp); public static FileInfo CreateTemporaryFile(string extension, out bool shouldCleanUpContainingFolder) { var tempFolder = GetTemporaryWorkingDirectory(out shouldCleanUpContainingFolder); Directory.CreateDirectory(tempFolder); string outputPath; do { outputPath = Path.Combine(tempFolder, Path.GetRandomFileName() + extension); } while (File.Exists(outputPath)); File.Create(outputPath); return new FileInfo(outputPath); } public static string SafeGetDirectoryName(string path, ILogger logger) { try { var dir = Path.GetDirectoryName(path); if (dir is null) { return ""; } if (!dir.EndsWith(Path.DirectorySeparatorChar)) { dir += Path.DirectorySeparatorChar; } return dir; } catch (Exception ex) { logger.LogDebug($"Failed to get directory name for {path}: {ex.Message}"); return ""; } } public static string? SafeGetFileName(string path, ILogger logger) { try { return Path.GetFileName(path); } catch (Exception ex) { logger.LogDebug($"Failed to get file name for {path}: {ex.Message}"); return null; } } } }