Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 14e98f7

Browse files
committed
Merge pull request #2410 from JeremyKuhne/AllowExtended
Allow extended syntax for paths
2 parents 70c1f09 + 72429b2 commit 14e98f7

28 files changed

+443
-279
lines changed
Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Diagnostics;
45
using System.Diagnostics.Contracts;
56

67
namespace System.IO
78
{
89
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
910
internal static partial class PathInternal
1011
{
12+
// Therre is only one invalid path character in Unix
13+
private const char InvalidPathChar = '\0';
14+
internal static readonly char[] InvalidPathChars = { InvalidPathChar };
15+
1116
/// <summary>Returns a value indicating if the given path contains invalid characters.</summary>
1217
internal static bool HasIllegalCharacters(string path, bool checkAdditional = false)
1318
{
1419
Contract.Requires(path != null);
1520

1621
foreach (char c in path)
1722
{
18-
// Same as Path.InvalidPathChars, unrolled here for performance
19-
if (c == '\0')
23+
// Same as InvalidPathChars, unrolled here for performance
24+
if (c == InvalidPathChar)
2025
return true;
21-
22-
// checkAdditional is meant to check for additional characters
23-
// permitted in a search path but disallowed in a normal path.
24-
// In Windows this is * and ?,but Unix permits such characters
25-
// in the filename so checkAdditional is ignored.
2626
}
2727

2828
return false;
2929
}
30+
31+
internal static int GetRootLength(string path)
32+
{
33+
PathInternal.CheckInvalidPathChars(path);
34+
return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
35+
}
36+
37+
internal static bool IsDirectorySeparator(char c)
38+
{
39+
// The alternatie directory separator char is the same as the directory separator,
40+
// so we only need to check one.
41+
Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
42+
return c == Path.DirectorySeparatorChar;
43+
}
3044
}
3145
}

src/Common/src/System/IO/PathInternal.Windows.cs

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@ namespace System.IO
88
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
99
internal static partial class PathInternal
1010
{
11+
internal const string LongPathPrefix = @"\\?\";
12+
internal const string UNCPathPrefix = @"\\";
13+
internal const string UNCLongPathPrefixToInsert = @"?\UNC\";
14+
internal const string UNCLongPathPrefix = @"\\?\UNC\";
15+
16+
internal static readonly char[] InvalidPathChars =
17+
{
18+
'\"', '<', '>', '|', '\0',
19+
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
20+
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
21+
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
22+
(char)31
23+
};
24+
25+
internal static readonly char[] InvalidPathCharsWithAdditionalChecks = // This is used by HasIllegalCharacters
26+
{
27+
'\"', '<', '>', '|', '\0',
28+
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
29+
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
30+
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
31+
(char)31, '*', '?'
32+
};
33+
1134
/// <summary>
1235
/// Returns a value indicating if the given path contains invalid characters (", &lt;, &gt;, |
1336
/// NUL, or any ASCII char whose integer representation is in the range of 1 through 31),
@@ -19,19 +42,67 @@ internal static bool HasIllegalCharacters(string path, bool checkAdditional = fa
1942

2043
Contract.Requires(path != null);
2144

22-
foreach (char c in path)
45+
// Question mark is a normal part of extended path syntax (\\?\)
46+
int startIndex = path.StartsWith(LongPathPrefix, StringComparison.Ordinal) ? LongPathPrefix.Length : 0;
47+
return path.IndexOfAny(checkAdditional ? InvalidPathCharsWithAdditionalChecks : InvalidPathChars, startIndex) >= 0;
48+
}
49+
50+
/// <summary>
51+
/// Gets the length of the root of the path (drive, share, etc.).
52+
/// </summary>
53+
internal static int GetRootLength(string path)
54+
{
55+
CheckInvalidPathChars(path);
56+
57+
int i = 0;
58+
int length = path.Length;
59+
int volumeSeparatorLength = 2; // Length to the colon "C:"
60+
int uncRootLength = 2; // Length to the start of the server name "\\"
61+
62+
bool extendedSyntax = path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
63+
bool extendedUncSyntax = extendedSyntax && path.StartsWith(UNCLongPathPrefix, StringComparison.Ordinal);
64+
if (extendedSyntax)
65+
{
66+
// Shift the position we look for the root from to account for the extended prefix
67+
if (extendedUncSyntax)
68+
{
69+
// "C:" -> "\\?\C:"
70+
uncRootLength = UNCLongPathPrefix.Length;
71+
}
72+
else
73+
{
74+
// "\\" -> "\\?\UNC\"
75+
volumeSeparatorLength += LongPathPrefix.Length;
76+
}
77+
}
78+
79+
if ((!extendedSyntax || extendedUncSyntax) && length > 0 && IsDirectorySeparator(path[0]))
2380
{
24-
// Note: Same as Path.InvalidPathChars, unrolled here for performance
25-
if (c == '\"' || c == '<' || c == '>' || c == '|' || c < 32)
26-
return true;
27-
28-
// used when path cannot contain search strings.
29-
if (checkAdditional &&
30-
(c == '?' || c == '*'))
31-
return true;
81+
// UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
82+
83+
i = 1; // Drive rooted (\foo) is one character
84+
if (extendedUncSyntax || (length > 1 && (IsDirectorySeparator(path[1]))))
85+
{
86+
// UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
87+
// (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
88+
i = uncRootLength;
89+
int n = 2; // Maximum separators to skip
90+
while (i < length && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
91+
}
92+
}
93+
else if (length >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
94+
{
95+
// Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
96+
// If the colon is followed by a directory separator, move past it
97+
i = volumeSeparatorLength;
98+
if (length >= volumeSeparatorLength + 1 && (IsDirectorySeparator(path[volumeSeparatorLength]))) i++;
3299
}
100+
return i;
101+
}
33102

34-
return false;
103+
internal static bool IsDirectorySeparator(char c)
104+
{
105+
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
35106
}
36107
}
37108
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Diagnostics.Contracts;
5+
6+
namespace System.IO
7+
{
8+
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
9+
internal static partial class PathInternal
10+
{
11+
/// <summary>
12+
/// Checks for invalid path characters in the given path.
13+
/// </summary>
14+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
15+
/// <exception cref="System.ArgumentException">Thrown if the path has invalid characters.</exception>
16+
/// <param name="path">The path to check for invalid characters.</param>
17+
/// <param name="checkAdditional">Set to true to check for characters that are only valid in specific contexts (such as search characters on Windows).</param>
18+
internal static void CheckInvalidPathChars(string path, bool checkAdditional = false)
19+
{
20+
if (path == null)
21+
throw new ArgumentNullException("path");
22+
23+
if (PathInternal.HasIllegalCharacters(path, checkAdditional))
24+
throw new ArgumentException(SR.Argument_InvalidPathChars, "path");
25+
}
26+
}
27+
}

src/System.IO.FileSystem.DriveInfo/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@
126126
<data name="Arg_InvalidDriveChars" xml:space="preserve">
127127
<value>Illegal characters in drive name '{0}'.</value>
128128
</data>
129+
<data name="Argument_InvalidPathChars" xml:space="preserve">
130+
<value>Illegal characters in path.</value>
131+
</data>
129132
<data name="ArgumentOutOfRange_FileLengthTooBig" xml:space="preserve">
130133
<value>Specified file length was too large for the file system.</value>
131134
</data>

src/System.IO.FileSystem.DriveInfo/src/System.IO.FileSystem.DriveInfo.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
<Compile Include="$(CommonPath)\System\__HResults.cs">
3333
<Link>Common\System\__HResults.cs</Link>
3434
</Compile>
35+
<Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
36+
<Link>Common\System\IO\PathInternal.cs</Link>
37+
</Compile>
3538
</ItemGroup>
3639
<ItemGroup Condition=" '$(TargetsWindows)' == 'true' ">
3740
<Compile Include="System\IO\__Error.cs" />

src/System.IO.FileSystem/src/System.IO.FileSystem.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
5353
<Link>Common\System\IO\StringBuilderCache.cs</Link>
5454
</Compile>
55+
<Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
56+
<Link>Common\System\IO\PathInternal.cs</Link>
57+
</Compile>
5558
</ItemGroup>
5659
<!-- Windows -->
5760
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">

src/System.IO.FileSystem/src/System/IO/Directory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,15 +486,15 @@ public static String GetDirectoryRoot(String path)
486486
Contract.EndContractBlock();
487487

488488
String fullPath = PathHelpers.GetFullPathInternal(path);
489-
String root = fullPath.Substring(0, PathHelpers.GetRootLength(fullPath));
489+
String root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath));
490490

491491
return root;
492492
}
493493

494494
internal static String InternalGetDirectoryRoot(String path)
495495
{
496496
if (path == null) return null;
497-
return path.Substring(0, PathHelpers.GetRootLength(path));
497+
return path.Substring(0, PathInternal.GetRootLength(path));
498498
}
499499

500500
/*===============================CurrentDirectory===============================

src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public DirectoryInfo(String path)
2525
[System.Security.SecuritySafeCritical]
2626
internal DirectoryInfo(String fullPath, IFileSystemObject fileSystemObject) : base(fileSystemObject)
2727
{
28-
Debug.Assert(PathHelpers.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
28+
Debug.Assert(PathInternal.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
2929

3030
// Fast path when we know a DirectoryInfo exists.
3131
OriginalPath = Path.GetFileName(fullPath);

src/System.IO.FileSystem/src/System/IO/File.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public static bool Exists(String path)
195195
// Otherwise, FillAttributeInfo removes it and we may return a false positive.
196196
// GetFullPath should never return null
197197
Debug.Assert(path != null, "File.Exists: GetFullPath returned null");
198-
if (path.Length > 0 && PathHelpers.IsDirectorySeparator(path[path.Length - 1]))
198+
if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]))
199199
{
200200
return false;
201201
}

src/System.IO.FileSystem/src/System/IO/FileStream.Win32.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ static partial void ValidatePath(string fullPath, string paramName)
2424
// Prevent access to your disk drives as raw block devices.
2525
if (fullPath.StartsWith("\\\\.\\", StringComparison.Ordinal))
2626
throw new ArgumentException(SR.Arg_DevicesNotSupported, paramName);
27-
28-
// Check for additional invalid characters. Most invalid characters were checked above
29-
// in our call to Path.GetFullPath(path);
30-
if (fullPath.IndexOfAny(s_additionalInvalidChars) != -1)
31-
throw new ArgumentException(SR.Argument_InvalidPathChars, paramName);
32-
33-
if (fullPath.IndexOf(':', 2) != -1)
34-
throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
3527
}
3628

3729
private static readonly char[] s_additionalInvalidChars = new[] { '?', '*' };

0 commit comments

Comments
 (0)