mirror of https://github.com/raandree/NTFSSecurity
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
26 KiB
614 lines
26 KiB
/* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security;
|
|
using System.Text;
|
|
|
|
namespace Alphaleonis.Win32.Filesystem
|
|
{
|
|
public static partial class Path
|
|
{
|
|
#region HasExtension (.NET)
|
|
|
|
/// <summary>Determines whether a path includes a file name extension.</summary>
|
|
/// <returns><see langword="true"/> if the characters that follow the last directory separator (\\ or /) or volume separator (:) in the path include a period (.) followed by one or more characters; otherwise, <see langword="false"/>.</returns>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <param name="path">The path to search for an extension. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
|
|
[SecurityCritical]
|
|
public static bool HasExtension(string path)
|
|
{
|
|
return System.IO.Path.HasExtension(path);
|
|
}
|
|
|
|
#endregion // HasExtension (.NET)
|
|
|
|
#region IsPathRooted
|
|
|
|
#region .NET
|
|
|
|
/// <summary>Gets a value indicating whether the specified path string contains absolute or relative path information.</summary>
|
|
/// <returns><see langword="true"/> if <paramref name="path"/> contains a root; otherwise, <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// The IsPathRooted method returns <see langword="true"/> if the first character is a directory separator character such as
|
|
/// <see cref="DirectorySeparatorChar"/>, or if the path starts with a drive letter and colon (<see cref="VolumeSeparatorChar"/>).
|
|
/// For example, it returns true for path strings such as "\\MyDir\\MyFile.txt", "C:\\MyDir", or "C:MyDir".
|
|
/// It returns <see langword="false"/> for path strings such as "MyDir".
|
|
/// </remarks>
|
|
/// <remarks>This method does not verify that the path or file name exists.</remarks>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <exception cref="ArgumentNullException"/>
|
|
/// <param name="path">The path to test. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
|
|
[SecurityCritical]
|
|
public static bool IsPathRooted(string path)
|
|
{
|
|
return IsPathRooted(path, true);
|
|
}
|
|
|
|
#endregion // .NET
|
|
|
|
/// <summary>[AlphaFS] Gets a value indicating whether the specified path string contains absolute or relative path information.</summary>
|
|
/// <returns><see langword="true"/> if <paramref name="path"/> contains a root; otherwise, <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// The IsPathRooted method returns true if the first character is a directory separator character such as
|
|
/// <see cref="DirectorySeparatorChar"/>, or if the path starts with a drive letter and colon (<see cref="VolumeSeparatorChar"/>).
|
|
/// For example, it returns <see langword="true"/> for path strings such as "\\MyDir\\MyFile.txt", "C:\\MyDir", or "C:MyDir".
|
|
/// It returns <see langword="false"/> for path strings such as "MyDir".
|
|
/// </remarks>
|
|
/// <remarks>This method does not verify that the path or file name exists.</remarks>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <exception cref="ArgumentNullException"/>
|
|
/// <param name="path">The path to test. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
|
|
/// <param name="checkInvalidPathChars"><see langword="true"/> will check <paramref name="path"/> for invalid path characters.</param>
|
|
[SecurityCritical]
|
|
public static bool IsPathRooted(string path, bool checkInvalidPathChars)
|
|
{
|
|
if (path != null)
|
|
{
|
|
if (checkInvalidPathChars)
|
|
CheckInvalidPathChars(path, false, true);
|
|
|
|
var length = path.Length;
|
|
|
|
if ((length >= 1 && IsDVsc(path[0], false)) ||
|
|
(length >= 2 && IsDVsc(path[1], true)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion // IsPathRooted
|
|
|
|
#region IsValidName
|
|
|
|
/// <summary>[AlphaFS] Check if file or folder name has any invalid characters.</summary>
|
|
/// <exception cref="ArgumentNullException"/>
|
|
/// <param name="name">File or folder name.</param>
|
|
/// <returns><see langword="true"/> if name contains any invalid characters. Otherwise <see langword="false"/></returns>
|
|
public static bool IsValidName(string name)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException("name");
|
|
|
|
return name.IndexOfAny(GetInvalidFileNameChars()) < 0;
|
|
}
|
|
|
|
#endregion // IsValidName
|
|
|
|
#region Internal Methods
|
|
|
|
internal static void CheckInvalidUncPath(string path)
|
|
{
|
|
// Tackle: Path.GetFullPath(@"\\\\.txt"), but exclude "." which is the current directory.
|
|
if (!IsLongPath(path) && path.StartsWith(UncPrefix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var tackle = GetRegularPathCore(path, GetFullPathOptions.None, false).TrimStart(DirectorySeparatorChar, AltDirectorySeparatorChar);
|
|
|
|
if (tackle.Length >= 2 && tackle[0] == CurrentDirectoryPrefixChar)
|
|
throw new ArgumentException(Resources.UNC_Path_Should_Match_Format);
|
|
}
|
|
}
|
|
|
|
/// <summary>Checks that the given path format is supported.</summary>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <exception cref="NotSupportedException"/>
|
|
/// <param name="path">A path to the file or directory.</param>
|
|
/// <param name="checkInvalidPathChars">Checks that the path contains only valid path-characters.</param>
|
|
/// <param name="checkAdditional">.</param>
|
|
internal static void CheckSupportedPathFormat(string path, bool checkInvalidPathChars, bool checkAdditional)
|
|
{
|
|
if (Utils.IsNullOrWhiteSpace(path) || path.Length < 2)
|
|
return;
|
|
|
|
var regularPath = GetRegularPathCore(path, GetFullPathOptions.None, false);
|
|
|
|
var isArgumentException = (regularPath[0] == VolumeSeparatorChar);
|
|
var throwException = (isArgumentException || (regularPath.Length >= 2 && regularPath.IndexOf(VolumeSeparatorChar, 2) != -1));
|
|
|
|
if (throwException)
|
|
{
|
|
if (isArgumentException)
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Unsupported_Path_Format, regularPath));
|
|
|
|
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.Unsupported_Path_Format, regularPath));
|
|
}
|
|
|
|
if (checkInvalidPathChars)
|
|
CheckInvalidPathChars(path, checkAdditional, false);
|
|
}
|
|
|
|
/// <summary>Checks that the path contains only valid path-characters.</summary>
|
|
/// <exception cref="ArgumentNullException"/>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <param name="path">A path to the file or directory.</param>
|
|
/// <param name="checkAdditional"><see langword="true"/> also checks for ? and * characters.</param>
|
|
/// <param name="allowEmpty">When <see langword="false"/>, throws an <see cref="ArgumentException"/>.</param>
|
|
[SecurityCritical]
|
|
private static void CheckInvalidPathChars(string path, bool checkAdditional, bool allowEmpty)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException("path");
|
|
|
|
if (!allowEmpty && (path.Length == 0 || Utils.IsNullOrWhiteSpace(path)))
|
|
throw new ArgumentException(Resources.Path_Is_Zero_Length_Or_Only_White_Space, "path");
|
|
|
|
// Will fail on a Unicode path.
|
|
var pathRp = GetRegularPathCore(path, GetFullPathOptions.None, allowEmpty);
|
|
|
|
|
|
// Handle "Path.GlobalRootPrefix" and "Path.VolumePrefix".
|
|
if (pathRp.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase))
|
|
pathRp = pathRp.Replace(GlobalRootPrefix, string.Empty);
|
|
|
|
if (pathRp.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase))
|
|
pathRp = pathRp.Replace(VolumePrefix, string.Empty);
|
|
|
|
|
|
for (int index = 0, l = pathRp.Length; index < l; ++index)
|
|
{
|
|
int num = pathRp[index];
|
|
switch (num)
|
|
{
|
|
case 34: // " (quote)
|
|
case 60: // < (less than)
|
|
case 62: // > (greater than)
|
|
case 124: // | (pipe)
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Illegal_Characters_In_Path, (char) num), pathRp);
|
|
|
|
default:
|
|
// 32: space
|
|
if (num >= 32 && (!checkAdditional || num != WildcardQuestionChar && num != WildcardStarMatchAllChar))
|
|
continue;
|
|
|
|
goto case 34;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Tranlates DosDevicePath, Volume GUID. For example: "\Device\HarddiskVolumeX\path\filename.ext" can translate to: "\path\filename.ext" or: "\\?\Volume{GUID}\path\filename.ext".</summary>
|
|
/// <returns>A translated dos path.</returns>
|
|
/// <param name="dosDevice">A DosDevicePath, for example: \Device\HarddiskVolumeX\path\filename.ext.</param>
|
|
/// <param name="deviceReplacement">Alternate path/device text, usually <c>string.Empty</c> or <see langword="null"/>.</param>
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
|
[SecurityCritical]
|
|
private static string DosDeviceToDosPath(string dosDevice, string deviceReplacement)
|
|
{
|
|
if (Utils.IsNullOrWhiteSpace(dosDevice))
|
|
return string.Empty;
|
|
|
|
foreach (var drive in Directory.EnumerateLogicalDrivesCore(false, false).Select(drv => drv.Name))
|
|
{
|
|
try
|
|
{
|
|
var path = RemoveTrailingDirectorySeparator(drive, false);
|
|
foreach (var devNt in Volume.QueryDosDevice(path).Where(dosDevice.StartsWith))
|
|
return dosDevice.Replace(devNt, deviceReplacement ?? path);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
return string.Empty;
|
|
}
|
|
|
|
[SecurityCritical]
|
|
internal static int GetRootLength(string path, bool checkInvalidPathChars)
|
|
{
|
|
if (checkInvalidPathChars)
|
|
CheckInvalidPathChars(path, false, false);
|
|
|
|
var index = 0;
|
|
var length = path.Length;
|
|
|
|
if (length >= 1 && IsDVsc(path[0], false))
|
|
{
|
|
index = 1;
|
|
if (length >= 2 && IsDVsc(path[1], false))
|
|
{
|
|
index = 2;
|
|
var num = 2;
|
|
|
|
while (index < length && (!IsDVsc(path[index], false) || --num > 0))
|
|
++index;
|
|
}
|
|
}
|
|
else if (length >= 2 && IsDVsc(path[1], true))
|
|
{
|
|
index = 2;
|
|
if (length >= 3 && IsDVsc(path[2], false))
|
|
++index;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
/// <summary>Check if <paramref name="c"/> is a directory- and/or volume-separator character.</summary>
|
|
/// <returns><see langword="true"/> if <paramref name="c"/> is a separator character.</returns>
|
|
/// <param name="c">The character to check.</param>
|
|
/// <param name="checkSeparatorChar">
|
|
/// If <see langword="null"/>, checks for all separator characters: <see cref="DirectorySeparatorChar"/>,
|
|
/// <see cref="AltDirectorySeparatorChar"/> and <see cref="VolumeSeparatorChar"/>
|
|
/// If <see langword="false"/>, only checks for: <see cref="DirectorySeparatorChar"/> and <see cref="AltDirectorySeparatorChar"/>
|
|
/// If <see langword="true"/> only checks for: <see cref="VolumeSeparatorChar"/>
|
|
/// </param>
|
|
[SecurityCritical]
|
|
internal static bool IsDVsc(char c, bool? checkSeparatorChar)
|
|
{
|
|
return checkSeparatorChar == null
|
|
|
|
// Check for all separator characters.
|
|
? c == DirectorySeparatorChar || c == AltDirectorySeparatorChar || c == VolumeSeparatorChar
|
|
|
|
// Check for some separator characters.
|
|
: ((bool)checkSeparatorChar
|
|
? c == VolumeSeparatorChar
|
|
: c == DirectorySeparatorChar || c == AltDirectorySeparatorChar);
|
|
}
|
|
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
private static string NormalizePath(string path, GetFullPathOptions options)
|
|
{
|
|
var newBuffer = new StringBuilder(NativeMethods.MaxPathUnicode);
|
|
var index = 0;
|
|
uint numSpaces = 0;
|
|
uint numDots = 0;
|
|
var fixupDirectorySeparator = false;
|
|
|
|
// Number of significant chars other than potentially suppressible
|
|
// dots and spaces since the last directory or volume separator char
|
|
uint numSigChars = 0;
|
|
|
|
var lastSigChar = -1; // Index of last significant character.
|
|
|
|
// Whether this segment of the path (not the complete path) started
|
|
// with a volume separator char. Reject "c:...".
|
|
var startedWithVolumeSeparator = false;
|
|
var firstSegment = true;
|
|
var lastDirectorySeparatorPos = 0;
|
|
|
|
// LEGACY: This code is here for backwards compatibility reasons. It
|
|
// ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
|
|
// turned into \foo.cs\bar.cs.
|
|
if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar))
|
|
{
|
|
newBuffer.Append('\\');
|
|
index++;
|
|
lastSigChar = 0;
|
|
}
|
|
|
|
// Normalize the string, stripping out redundant dots, spaces, and slashes.
|
|
while (index < path.Length)
|
|
{
|
|
var currentChar = path[index];
|
|
|
|
// We handle both directory separators and dots specially. For
|
|
// directory separators, we consume consecutive appearances.
|
|
// For dots, we consume all dots beyond the second in
|
|
// succession. All other characters are added as is. In
|
|
// addition we consume all spaces after the last other char
|
|
// in a directory name up until the directory separator.
|
|
|
|
if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar)
|
|
{
|
|
// If we have a path like "123.../foo", remove the trailing dots.
|
|
// However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
|
|
// Also remove trailing spaces from both files & directory names.
|
|
// This was agreed on with the OS team to fix undeletable directory
|
|
// names ending in spaces.
|
|
|
|
// If we saw a '\' as the previous last significant character and
|
|
// are simply going to write out dots, suppress them.
|
|
// If we only contain dots and slashes though, only allow
|
|
// a string like [dot]+ [space]*. Ignore everything else.
|
|
// Legal: "\.. \", "\...\", "\. \"
|
|
// Illegal: "\.. .\", "\. .\", "\ .\"
|
|
|
|
if (numSigChars == 0)
|
|
{
|
|
// Dot and space handling
|
|
if (numDots > 0)
|
|
{
|
|
// Look for ".[space]*" or "..[space]*"
|
|
var start = lastSigChar + 1;
|
|
if (path[start] != CurrentDirectoryPrefixChar)
|
|
throw new ArgumentException(path);
|
|
|
|
// Only allow "[dot]+[space]*", and normalize the
|
|
// legal ones to "." or ".."
|
|
if (numDots >= 2)
|
|
{
|
|
// Reject "C:..."
|
|
if (startedWithVolumeSeparator && numDots > 2)
|
|
throw new ArgumentException(path);
|
|
|
|
|
|
if (path[start + 1] == CurrentDirectoryPrefixChar)
|
|
{
|
|
// Search for a space in the middle of the dots and throw
|
|
for (var i = start + 2; i < start + numDots; i++)
|
|
{
|
|
if (path[i] != CurrentDirectoryPrefixChar)
|
|
throw new ArgumentException(path);
|
|
}
|
|
|
|
numDots = 2;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (numDots > 1)
|
|
throw new ArgumentException(path);
|
|
|
|
numDots = 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (numDots == 2)
|
|
newBuffer.Append(CurrentDirectoryPrefixChar);
|
|
|
|
|
|
newBuffer.Append(CurrentDirectoryPrefixChar);
|
|
fixupDirectorySeparator = false;
|
|
|
|
// Continue in this case, potentially writing out '\'.
|
|
}
|
|
|
|
|
|
if (numSpaces > 0 && firstSegment)
|
|
{
|
|
// Handle strings like " \\server\share".
|
|
if (index + 1 < path.Length && (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar))
|
|
newBuffer.Append(DirectorySeparatorChar);
|
|
}
|
|
}
|
|
|
|
|
|
numDots = 0;
|
|
numSpaces = 0; // Suppress trailing spaces
|
|
|
|
if (!fixupDirectorySeparator)
|
|
{
|
|
fixupDirectorySeparator = true;
|
|
newBuffer.Append(DirectorySeparatorChar);
|
|
}
|
|
|
|
numSigChars = 0;
|
|
lastSigChar = index;
|
|
startedWithVolumeSeparator = false;
|
|
firstSegment = false;
|
|
|
|
|
|
var thisPos = newBuffer.Length - 1;
|
|
if (thisPos - lastDirectorySeparatorPos > NativeMethods.MaxDirectoryLength)
|
|
throw new PathTooLongException(path);
|
|
|
|
lastDirectorySeparatorPos = thisPos;
|
|
} // if (Found directory separator)
|
|
|
|
else if (currentChar == CurrentDirectoryPrefixChar)
|
|
{
|
|
// Reduce only multiple .'s only after slash to 2 dots. For
|
|
// instance a...b is a valid file name.
|
|
numDots++;
|
|
// Don't flush out non-terminal spaces here, because they may in
|
|
// the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
|
|
// which is the conclusion of removing trailing dots & spaces,
|
|
// as well as folding multiple '\' characters.
|
|
}
|
|
|
|
else if (currentChar == ' ')
|
|
numSpaces++;
|
|
|
|
else
|
|
{ // Normal character logic
|
|
fixupDirectorySeparator = false;
|
|
|
|
// To reject strings like "C:...\foo" and "C :\foo"
|
|
if (firstSegment && currentChar == VolumeSeparatorChar)
|
|
{
|
|
// Only accept "C:", not "c :" or ":"
|
|
// Get a drive letter or ' ' if index is 0.
|
|
var driveLetter = (index > 0) ? path[index - 1] : ' ';
|
|
|
|
var validPath = (numDots == 0) && (numSigChars >= 1) && (driveLetter != ' ');
|
|
if (!validPath)
|
|
throw new ArgumentException(path);
|
|
|
|
startedWithVolumeSeparator = true;
|
|
// We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
|
|
if (numSigChars > 1)
|
|
{
|
|
// Common case, simply do nothing
|
|
var spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
|
|
while ((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ')
|
|
spaceCount++;
|
|
|
|
if (numSigChars - spaceCount == 1)
|
|
{
|
|
//Safe to update stack ptr directly
|
|
newBuffer.Length = 0;
|
|
newBuffer.Append(driveLetter);
|
|
// Overwrite spaces, we need a special case to not break " foo" as a relative path.
|
|
}
|
|
}
|
|
|
|
numSigChars = 0;
|
|
}
|
|
|
|
else
|
|
numSigChars += 1 + numDots + numSpaces;
|
|
|
|
// Copy any spaces & dots since the last significant character
|
|
// to here. Note we only counted the number of dots & spaces,
|
|
// and don't know what order they're in. Hence the copy.
|
|
if (numDots > 0 || numSpaces > 0)
|
|
{
|
|
var numCharsToCopy = lastSigChar >= 0 ? index - lastSigChar - 1 : index;
|
|
|
|
if (numCharsToCopy > 0)
|
|
for (var i = 0; i < numCharsToCopy; i++)
|
|
newBuffer.Append(path[lastSigChar + 1 + i]);
|
|
|
|
numDots = 0;
|
|
numSpaces = 0;
|
|
}
|
|
|
|
newBuffer.Append(currentChar);
|
|
lastSigChar = index;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
|
|
if (newBuffer.Length - 1 - lastDirectorySeparatorPos > NativeMethods.MaxDirectoryLength)
|
|
throw new PathTooLongException(path);
|
|
|
|
|
|
// Drop any trailing dots and spaces from file & directory names, EXCEPT
|
|
// we MUST make sure that "C:\foo\.." is correctly handled.
|
|
// Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
|
|
if (numSigChars == 0)
|
|
{
|
|
if (numDots > 0)
|
|
{
|
|
// Look for ".[space]*" or "..[space]*"
|
|
var start = lastSigChar + 1;
|
|
|
|
if (path[start] != CurrentDirectoryPrefixChar)
|
|
throw new ArgumentException(path);
|
|
|
|
|
|
// Only allow "[dot]+[space]*", and normalize the legal ones to "." or ".."
|
|
if (numDots >= 2)
|
|
{
|
|
// Reject "C:..."
|
|
if (startedWithVolumeSeparator && numDots > 2)
|
|
throw new ArgumentException(path);
|
|
|
|
|
|
if (path[start + 1] == CurrentDirectoryPrefixChar)
|
|
{
|
|
// Search for a space in the middle of the dots and throw
|
|
for (var i = start + 2; i < start + numDots; i++)
|
|
if (path[i] != CurrentDirectoryPrefixChar)
|
|
throw new ArgumentException(path);
|
|
|
|
numDots = 2;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (numDots > 1)
|
|
throw new ArgumentException(path);
|
|
|
|
numDots = 1;
|
|
}
|
|
}
|
|
|
|
if (numDots == 2)
|
|
newBuffer.Append(CurrentDirectoryPrefixChar);
|
|
|
|
newBuffer.Append(CurrentDirectoryPrefixChar);
|
|
}
|
|
}
|
|
|
|
|
|
// If we ended up eating all the characters, bail out.
|
|
if (newBuffer.Length == 0)
|
|
throw new ArgumentException(path);
|
|
|
|
|
|
// Disallow URL's here. Some of our other Win32 API calls will reject
|
|
// them later, so we might be better off rejecting them here.
|
|
// Note we've probably turned them into "file:\D:\foo.tmp" by now.
|
|
// But for compatibility, ensure that callers that aren't doing a
|
|
// full check aren't rejected here.
|
|
if ((options & GetFullPathOptions.FullCheck) != 0)
|
|
{
|
|
var newBufferString = newBuffer.ToString();
|
|
if (newBufferString.StartsWith("http:", StringComparison.OrdinalIgnoreCase) || newBufferString.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException(path);
|
|
}
|
|
|
|
// Call the Win32 API to do the final canonicalization step.
|
|
var result = 1;
|
|
|
|
|
|
if (result != 0)
|
|
{
|
|
/* Throw an ArgumentException for paths like \\, \\server, \\server\
|
|
This check can only be properly done after normalizing, so
|
|
\\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\
|
|
(an internal kernel path) because it provides aliases for drives. */
|
|
if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\')
|
|
{
|
|
var startIndex = 2;
|
|
while (startIndex < result)
|
|
{
|
|
if (newBuffer[startIndex] == '\\')
|
|
{
|
|
startIndex++;
|
|
break;
|
|
}
|
|
|
|
startIndex++;
|
|
}
|
|
|
|
if (startIndex == result)
|
|
throw new ArgumentException(path);
|
|
}
|
|
}
|
|
|
|
|
|
return newBuffer.ToString();
|
|
}
|
|
|
|
#endregion // Internal Methods
|
|
}
|
|
}
|
|
|