-
Notifications
You must be signed in to change notification settings - Fork 97
/
Copy pathSafeFileEnumerable.cs
149 lines (125 loc) · 5.65 KB
/
SafeFileEnumerable.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
namespace Microsoft.ComponentDetection.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Enumeration;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.Extensions.Logging;
public class SafeFileEnumerable : IEnumerable<MatchedFile>
{
private readonly IEnumerable<string> searchPatterns;
private readonly ExcludeDirectoryPredicate directoryExclusionPredicate;
private readonly DirectoryInfo directory;
private readonly IPathUtilityService pathUtilityService;
private readonly bool recursivelyScanDirectories;
private readonly Func<FileInfo, bool> fileMatchingPredicate;
private readonly EnumerationOptions enumerationOptions;
private readonly HashSet<string> enumeratedDirectories;
private readonly ILogger logger;
public SafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
{
this.directory = directory;
this.logger = logger;
this.searchPatterns = searchPatterns;
this.directoryExclusionPredicate = directoryExclusionPredicate;
this.recursivelyScanDirectories = recursivelyScanDirectories;
this.pathUtilityService = pathUtilityService;
this.enumeratedDirectories = previouslyEnumeratedDirectories;
this.enumerationOptions = new EnumerationOptions()
{
IgnoreInaccessible = true,
RecurseSubdirectories = this.recursivelyScanDirectories,
ReturnSpecialDirectories = false,
MatchType = MatchType.Simple,
};
}
public SafeFileEnumerable(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
: this(directory, ["*"], logger, pathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories, previouslyEnumeratedDirectories) => this.fileMatchingPredicate = fileMatchingPredicate;
public IEnumerator<MatchedFile> GetEnumerator()
{
var previouslyEnumeratedDirectories = this.enumeratedDirectories ?? [];
var fse = new FileSystemEnumerable<MatchedFile>(
this.directory.FullName,
(ref FileSystemEntry entry) =>
{
if (!(entry.ToFileSystemInfo() is FileInfo fi))
{
throw new InvalidOperationException("Encountered directory when expecting a file");
}
var foundPattern = entry.FileName.ToString();
foreach (var searchPattern in this.searchPatterns)
{
if (PathUtilityService.MatchesPattern(searchPattern, ref entry))
{
foundPattern = searchPattern;
}
}
return new MatchedFile() { File = fi, Pattern = foundPattern };
},
this.enumerationOptions)
{
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
{
if (entry.IsDirectory)
{
return false;
}
foreach (var searchPattern in this.searchPatterns)
{
if (PathUtilityService.MatchesPattern(searchPattern, ref entry))
{
return true;
}
}
return false;
},
ShouldRecursePredicate = (ref FileSystemEntry entry) =>
{
if (!this.recursivelyScanDirectories)
{
return false;
}
var targetPath = entry.ToFullPath();
var seenPreviously = false;
if (entry.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
var realPath = this.pathUtilityService.ResolvePhysicalPath(targetPath);
seenPreviously = previouslyEnumeratedDirectories.Contains(realPath);
previouslyEnumeratedDirectories.Add(realPath);
if (realPath.StartsWith(targetPath))
{
return false;
}
}
else if (previouslyEnumeratedDirectories.Contains(targetPath))
{
seenPreviously = true;
}
previouslyEnumeratedDirectories.Add(targetPath);
if (seenPreviously)
{
this.logger.LogDebug("Encountered real path {TargetPath} before. Short-Circuiting directory traversal", targetPath);
return false;
}
// This is actually a *directory* name (not FileName) and the directory containing that directory.
if (entry.IsDirectory && this.directoryExclusionPredicate != null && this.directoryExclusionPredicate(entry.FileName, entry.Directory))
{
return false;
}
return true;
},
};
foreach (var file in fse)
{
if (this.fileMatchingPredicate == null || this.fileMatchingPredicate(file.File))
{
yield return file;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}