forked from TestableIO/System.IO.Abstractions
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPathVerifier.cs
More file actions
186 lines (156 loc) · 6.13 KB
/
PathVerifier.cs
File metadata and controls
186 lines (156 loc) · 6.13 KB
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
using System;
using System.Linq;
namespace System.IO.Abstractions.TestingHelpers;
using XFS = MockUnixSupport;
/// <summary>
/// Provides helper methods for verifying paths.
/// </summary>
#if FEATURE_SERIALIZABLE
[Serializable]
#endif
public class PathVerifier
{
private static readonly char[] AdditionalInvalidPathChars = { '*', '?' };
private readonly IMockFileDataAccessor _mockFileDataAccessor;
// Windows supports extended-length paths with a `\\?\` prefix, to work around low path length limits.
// Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
private const string WINDOWS_EXTENDED_LENGTH_PATH_PREFIX = @"\\?\";
/// <summary>
/// Creates a new verifier instance.
/// </summary>
public PathVerifier(IMockFileDataAccessor mockFileDataAccessor)
{
_mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor));
}
/// <summary>
/// Determines whether the given path is legal.
/// </summary>
public void IsLegalAbsoluteOrRelative(string path, string paramName)
{
if (path == null)
{
throw new ArgumentNullException(paramName, StringResources.Manager.GetString("VALUE_CANNOT_BE_NULL"));
}
if (path == string.Empty)
{
throw new ArgumentException("Empty file name is not legal.", paramName);
}
if (path.Trim() == string.Empty)
{
throw CommonExceptions.PathIsNotOfALegalForm(paramName);
}
if (XFS.IsWindowsPlatform() && !IsValidUseOfVolumeSeparatorChar(path))
{
throw CommonExceptions.InvalidUseOfVolumeSeparator();
}
if (ExtractFileName(path).IndexOfAny(_mockFileDataAccessor.Path.GetInvalidFileNameChars()) > -1)
{
throw CommonExceptions.IllegalCharactersInPath();
}
var filePath = ExtractFilePath(path);
if (HasIllegalCharacters(filePath, checkAdditional: false))
{
throw CommonExceptions.IllegalCharactersInPath();
}
}
private static bool IsValidUseOfVolumeSeparatorChar(string path)
{
if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
{
// Skip over the `\\?\` prefix if there is one.
path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
}
var lastVolSepIndex = path.LastIndexOf(Path.VolumeSeparatorChar);
return lastVolSepIndex == -1 || lastVolSepIndex == 1 && char.IsLetter(path[0]);
}
private string ExtractFileName(string fullFileName)
{
return fullFileName.Split(
_mockFileDataAccessor.Path.DirectorySeparatorChar,
_mockFileDataAccessor.Path.AltDirectorySeparatorChar).Last();
}
private string ExtractFilePath(string fullFileName)
{
var extractFilePath = fullFileName.Split(
_mockFileDataAccessor.Path.DirectorySeparatorChar,
_mockFileDataAccessor.Path.AltDirectorySeparatorChar);
return string.Join(_mockFileDataAccessor.Path.DirectorySeparatorChar.ToString(), extractFilePath.Take(extractFilePath.Length - 1));
}
/// <summary>
/// Determines whether the given path contains illegal characters.
/// </summary>
public bool HasIllegalCharacters(string path, bool checkAdditional)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
var invalidPathChars = _mockFileDataAccessor.Path.GetInvalidPathChars();
if (checkAdditional)
{
// AdditionalInvalidPathChars includes '?', but this character is allowed in extended-length
// windows path prefixes (`\\?\`). If we're dealing with such a path, check for invalid
// characters after the prefix.
if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
{
path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
}
return path.IndexOfAny(invalidPathChars.Concat(AdditionalInvalidPathChars).ToArray()) >= 0;
}
return path.IndexOfAny(invalidPathChars) >= 0;
}
/// <summary>
/// Throws an excpetion if the given path contains invalid characters.
/// </summary>
public void CheckInvalidPathChars(string path, bool checkAdditional = false)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
if (HasIllegalCharacters(path, checkAdditional))
{
throw CommonExceptions.IllegalCharactersInPath();
}
}
/// <summary>
/// Determines the normalized drive name used for drive identification.
/// </summary>
/// <exception cref="ArgumentException">Thrown if the <paramref name="name"/> is not a valid drive name.</exception>
public string NormalizeDriveName(string name)
{
return TryNormalizeDriveName(name, out var result)
? result
: throw new ArgumentException(
@"Object must be a root directory (""C:\"") or a drive letter (""C"").");
}
/// <summary>
/// Tries to determine the normalized drive name used for drive identification.
/// </summary>
public bool TryNormalizeDriveName(string name, out string result)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
const string DRIVE_SEPARATOR = @":\";
if (name.Length == 1
|| (name.Length == 2 && name[1] == ':')
|| (name.Length == 3 && _mockFileDataAccessor.StringOperations.EndsWith(name, DRIVE_SEPARATOR)))
{
name = name[0] + DRIVE_SEPARATOR;
}
else
{
CheckInvalidPathChars(name);
name = _mockFileDataAccessor.Path.GetPathRoot(name);
if (string.IsNullOrEmpty(name) || _mockFileDataAccessor.StringOperations.StartsWith(name, @"\\"))
{
result = null;
return false;
}
}
result = name;
return true;
}
}