-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathFileUtils.cs
280 lines (248 loc) · 9.85 KB
/
FileUtils.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
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('/', '\\');
}
/// <summary>
/// Moves the source file to the destination, overwriting the destination file if
/// it exists already.
/// </summary>
/// <param name="src">Source file.</param>
/// <param name="dest">Target file.</param>
public static void MoveOrReplace(string src, string dest)
{
File.Move(src, dest, overwrite: true);
}
/// <summary>
/// Attempt to delete the given file (ignoring I/O exceptions).
/// </summary>
/// <param name="file">The file to delete.</param>
public static void TryDelete(string file)
{
try
{
File.Delete(file);
}
catch (IOException)
{
// Ignore
}
}
/// <summary>
/// Finds the path for the program <paramref name="prog"/> based on the
/// <code>PATH</code> environment variable, and in the case of Windows the
/// <code>PATHEXT</code> environment variable.
///
/// Returns <code>null</code> of no path can be found.
/// </summary>
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();
}
/// <summary>
/// Computes the hash of the file at <paramref name="filePath"/>.
/// </summary>
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);
}
/// <summary>
/// Computes the hash of <paramref name="input"/>.
/// </summary>
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, HttpClient httpClient, CancellationToken token)
{
using var contentStream = await httpClient.GetStreamAsync(address, token);
using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
await contentStream.CopyToAsync(stream, token);
}
private static void DownloadFileWithRetry(string address, string fileName, int tryCount, int timeoutMilliSeconds, ILogger logger)
{
logger.LogDebug($"Downloading {address} to {fileName}.");
using HttpClient client = new();
for (var i = 0; i < tryCount; i++)
{
logger.LogDebug($"Attempt {i + 1} of {tryCount}. Timeout: {timeoutMilliSeconds} ms.");
using var cts = new CancellationTokenSource();
cts.CancelAfter(timeoutMilliSeconds);
try
{
DownloadFileAsync(address, fileName, client, cts.Token).GetAwaiter().GetResult();
logger.LogDebug($"Downloaded {address} to {fileName}.");
return;
}
catch (Exception exc)
{
logger.LogDebug($"Failed to download {address} to {fileName}. Exception: {exc.Message}");
timeoutMilliSeconds *= 2;
if (i == tryCount - 1)
{
logger.LogDebug($"Failed to download {address} to {fileName} after {tryCount} attempts.");
// Rethrowing the last exception
throw;
}
}
}
}
/// <summary>
/// Downloads the file at <paramref name="address"/> to <paramref name="fileName"/>.
/// </summary>
public static void DownloadFile(string address, string fileName, ILogger logger) =>
DownloadFileWithRetry(address, fileName, tryCount: 3, timeoutMilliSeconds: 10000, logger);
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<string> tempFolderPath = new Lazy<string>(() =>
{
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<string, string?> 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;
}
}
}
}