Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

615 řádky
26 KiB

  1. /* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. using System;
  22. using System.Diagnostics.CodeAnalysis;
  23. using System.Globalization;
  24. using System.IO;
  25. using System.Linq;
  26. using System.Security;
  27. using System.Text;
  28. namespace Alphaleonis.Win32.Filesystem
  29. {
  30. public static partial class Path
  31. {
  32. #region HasExtension (.NET)
  33. /// <summary>Determines whether a path includes a file name extension.</summary>
  34. /// <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>
  35. /// <exception cref="ArgumentException"/>
  36. /// <param name="path">The path to search for an extension. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
  37. [SecurityCritical]
  38. public static bool HasExtension(string path)
  39. {
  40. return System.IO.Path.HasExtension(path);
  41. }
  42. #endregion // HasExtension (.NET)
  43. #region IsPathRooted
  44. #region .NET
  45. /// <summary>Gets a value indicating whether the specified path string contains absolute or relative path information.</summary>
  46. /// <returns><see langword="true"/> if <paramref name="path"/> contains a root; otherwise, <see langword="false"/>.</returns>
  47. /// <remarks>
  48. /// The IsPathRooted method returns <see langword="true"/> if the first character is a directory separator character such as
  49. /// <see cref="DirectorySeparatorChar"/>, or if the path starts with a drive letter and colon (<see cref="VolumeSeparatorChar"/>).
  50. /// For example, it returns true for path strings such as "\\MyDir\\MyFile.txt", "C:\\MyDir", or "C:MyDir".
  51. /// It returns <see langword="false"/> for path strings such as "MyDir".
  52. /// </remarks>
  53. /// <remarks>This method does not verify that the path or file name exists.</remarks>
  54. /// <exception cref="ArgumentException"/>
  55. /// <exception cref="ArgumentNullException"/>
  56. /// <param name="path">The path to test. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
  57. [SecurityCritical]
  58. public static bool IsPathRooted(string path)
  59. {
  60. return IsPathRooted(path, true);
  61. }
  62. #endregion // .NET
  63. /// <summary>[AlphaFS] Gets a value indicating whether the specified path string contains absolute or relative path information.</summary>
  64. /// <returns><see langword="true"/> if <paramref name="path"/> contains a root; otherwise, <see langword="false"/>.</returns>
  65. /// <remarks>
  66. /// The IsPathRooted method returns true if the first character is a directory separator character such as
  67. /// <see cref="DirectorySeparatorChar"/>, or if the path starts with a drive letter and colon (<see cref="VolumeSeparatorChar"/>).
  68. /// For example, it returns <see langword="true"/> for path strings such as "\\MyDir\\MyFile.txt", "C:\\MyDir", or "C:MyDir".
  69. /// It returns <see langword="false"/> for path strings such as "MyDir".
  70. /// </remarks>
  71. /// <remarks>This method does not verify that the path or file name exists.</remarks>
  72. /// <exception cref="ArgumentException"/>
  73. /// <exception cref="ArgumentNullException"/>
  74. /// <param name="path">The path to test. The path cannot contain any of the characters defined in <see cref="GetInvalidPathChars"/>.</param>
  75. /// <param name="checkInvalidPathChars"><see langword="true"/> will check <paramref name="path"/> for invalid path characters.</param>
  76. [SecurityCritical]
  77. public static bool IsPathRooted(string path, bool checkInvalidPathChars)
  78. {
  79. if (path != null)
  80. {
  81. if (checkInvalidPathChars)
  82. CheckInvalidPathChars(path, false, true);
  83. var length = path.Length;
  84. if ((length >= 1 && IsDVsc(path[0], false)) ||
  85. (length >= 2 && IsDVsc(path[1], true)))
  86. return true;
  87. }
  88. return false;
  89. }
  90. #endregion // IsPathRooted
  91. #region IsValidName
  92. /// <summary>[AlphaFS] Check if file or folder name has any invalid characters.</summary>
  93. /// <exception cref="ArgumentNullException"/>
  94. /// <param name="name">File or folder name.</param>
  95. /// <returns><see langword="true"/> if name contains any invalid characters. Otherwise <see langword="false"/></returns>
  96. public static bool IsValidName(string name)
  97. {
  98. if (name == null)
  99. throw new ArgumentNullException("name");
  100. return name.IndexOfAny(GetInvalidFileNameChars()) < 0;
  101. }
  102. #endregion // IsValidName
  103. #region Internal Methods
  104. internal static void CheckInvalidUncPath(string path)
  105. {
  106. // Tackle: Path.GetFullPath(@"\\\\.txt"), but exclude "." which is the current directory.
  107. if (!IsLongPath(path) && path.StartsWith(UncPrefix, StringComparison.OrdinalIgnoreCase))
  108. {
  109. var tackle = GetRegularPathCore(path, GetFullPathOptions.None, false).TrimStart(DirectorySeparatorChar, AltDirectorySeparatorChar);
  110. if (tackle.Length >= 2 && tackle[0] == CurrentDirectoryPrefixChar)
  111. throw new ArgumentException(Resources.UNC_Path_Should_Match_Format);
  112. }
  113. }
  114. /// <summary>Checks that the given path format is supported.</summary>
  115. /// <exception cref="ArgumentException"/>
  116. /// <exception cref="NotSupportedException"/>
  117. /// <param name="path">A path to the file or directory.</param>
  118. /// <param name="checkInvalidPathChars">Checks that the path contains only valid path-characters.</param>
  119. /// <param name="checkAdditional">.</param>
  120. internal static void CheckSupportedPathFormat(string path, bool checkInvalidPathChars, bool checkAdditional)
  121. {
  122. if (Utils.IsNullOrWhiteSpace(path) || path.Length < 2)
  123. return;
  124. var regularPath = GetRegularPathCore(path, GetFullPathOptions.None, false);
  125. var isArgumentException = (regularPath[0] == VolumeSeparatorChar);
  126. var throwException = (isArgumentException || (regularPath.Length >= 2 && regularPath.IndexOf(VolumeSeparatorChar, 2) != -1));
  127. if (throwException)
  128. {
  129. if (isArgumentException)
  130. throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Unsupported_Path_Format, regularPath));
  131. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.Unsupported_Path_Format, regularPath));
  132. }
  133. if (checkInvalidPathChars)
  134. CheckInvalidPathChars(path, checkAdditional, false);
  135. }
  136. /// <summary>Checks that the path contains only valid path-characters.</summary>
  137. /// <exception cref="ArgumentNullException"/>
  138. /// <exception cref="ArgumentException"/>
  139. /// <param name="path">A path to the file or directory.</param>
  140. /// <param name="checkAdditional"><see langword="true"/> also checks for ? and * characters.</param>
  141. /// <param name="allowEmpty">When <see langword="false"/>, throws an <see cref="ArgumentException"/>.</param>
  142. [SecurityCritical]
  143. private static void CheckInvalidPathChars(string path, bool checkAdditional, bool allowEmpty)
  144. {
  145. if (path == null)
  146. throw new ArgumentNullException("path");
  147. if (!allowEmpty && (path.Length == 0 || Utils.IsNullOrWhiteSpace(path)))
  148. throw new ArgumentException(Resources.Path_Is_Zero_Length_Or_Only_White_Space, "path");
  149. // Will fail on a Unicode path.
  150. var pathRp = GetRegularPathCore(path, GetFullPathOptions.None, allowEmpty);
  151. // Handle "Path.GlobalRootPrefix" and "Path.VolumePrefix".
  152. if (pathRp.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase))
  153. pathRp = pathRp.Replace(GlobalRootPrefix, string.Empty);
  154. if (pathRp.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase))
  155. pathRp = pathRp.Replace(VolumePrefix, string.Empty);
  156. for (int index = 0, l = pathRp.Length; index < l; ++index)
  157. {
  158. int num = pathRp[index];
  159. switch (num)
  160. {
  161. case 34: // " (quote)
  162. case 60: // < (less than)
  163. case 62: // > (greater than)
  164. case 124: // | (pipe)
  165. throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Illegal_Characters_In_Path, (char) num), pathRp);
  166. default:
  167. // 32: space
  168. if (num >= 32 && (!checkAdditional || num != WildcardQuestionChar && num != WildcardStarMatchAllChar))
  169. continue;
  170. goto case 34;
  171. }
  172. }
  173. }
  174. /// <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>
  175. /// <returns>A translated dos path.</returns>
  176. /// <param name="dosDevice">A DosDevicePath, for example: \Device\HarddiskVolumeX\path\filename.ext.</param>
  177. /// <param name="deviceReplacement">Alternate path/device text, usually <c>string.Empty</c> or <see langword="null"/>.</param>
  178. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  179. [SecurityCritical]
  180. private static string DosDeviceToDosPath(string dosDevice, string deviceReplacement)
  181. {
  182. if (Utils.IsNullOrWhiteSpace(dosDevice))
  183. return string.Empty;
  184. foreach (var drive in Directory.EnumerateLogicalDrivesCore(false, false).Select(drv => drv.Name))
  185. {
  186. try
  187. {
  188. var path = RemoveTrailingDirectorySeparator(drive, false);
  189. foreach (var devNt in Volume.QueryDosDevice(path).Where(dosDevice.StartsWith))
  190. return dosDevice.Replace(devNt, deviceReplacement ?? path);
  191. }
  192. catch
  193. {
  194. }
  195. }
  196. return string.Empty;
  197. }
  198. [SecurityCritical]
  199. internal static int GetRootLength(string path, bool checkInvalidPathChars)
  200. {
  201. if (checkInvalidPathChars)
  202. CheckInvalidPathChars(path, false, false);
  203. var index = 0;
  204. var length = path.Length;
  205. if (length >= 1 && IsDVsc(path[0], false))
  206. {
  207. index = 1;
  208. if (length >= 2 && IsDVsc(path[1], false))
  209. {
  210. index = 2;
  211. var num = 2;
  212. while (index < length && (!IsDVsc(path[index], false) || --num > 0))
  213. ++index;
  214. }
  215. }
  216. else if (length >= 2 && IsDVsc(path[1], true))
  217. {
  218. index = 2;
  219. if (length >= 3 && IsDVsc(path[2], false))
  220. ++index;
  221. }
  222. return index;
  223. }
  224. /// <summary>Check if <paramref name="c"/> is a directory- and/or volume-separator character.</summary>
  225. /// <returns><see langword="true"/> if <paramref name="c"/> is a separator character.</returns>
  226. /// <param name="c">The character to check.</param>
  227. /// <param name="checkSeparatorChar">
  228. /// If <see langword="null"/>, checks for all separator characters: <see cref="DirectorySeparatorChar"/>,
  229. /// <see cref="AltDirectorySeparatorChar"/> and <see cref="VolumeSeparatorChar"/>
  230. /// If <see langword="false"/>, only checks for: <see cref="DirectorySeparatorChar"/> and <see cref="AltDirectorySeparatorChar"/>
  231. /// If <see langword="true"/> only checks for: <see cref="VolumeSeparatorChar"/>
  232. /// </param>
  233. [SecurityCritical]
  234. internal static bool IsDVsc(char c, bool? checkSeparatorChar)
  235. {
  236. return checkSeparatorChar == null
  237. // Check for all separator characters.
  238. ? c == DirectorySeparatorChar || c == AltDirectorySeparatorChar || c == VolumeSeparatorChar
  239. // Check for some separator characters.
  240. : ((bool)checkSeparatorChar
  241. ? c == VolumeSeparatorChar
  242. : c == DirectorySeparatorChar || c == AltDirectorySeparatorChar);
  243. }
  244. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  245. private static string NormalizePath(string path, GetFullPathOptions options)
  246. {
  247. var newBuffer = new StringBuilder(NativeMethods.MaxPathUnicode);
  248. var index = 0;
  249. uint numSpaces = 0;
  250. uint numDots = 0;
  251. var fixupDirectorySeparator = false;
  252. // Number of significant chars other than potentially suppressible
  253. // dots and spaces since the last directory or volume separator char
  254. uint numSigChars = 0;
  255. var lastSigChar = -1; // Index of last significant character.
  256. // Whether this segment of the path (not the complete path) started
  257. // with a volume separator char. Reject "c:...".
  258. var startedWithVolumeSeparator = false;
  259. var firstSegment = true;
  260. var lastDirectorySeparatorPos = 0;
  261. // LEGACY: This code is here for backwards compatibility reasons. It
  262. // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
  263. // turned into \foo.cs\bar.cs.
  264. if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar))
  265. {
  266. newBuffer.Append('\\');
  267. index++;
  268. lastSigChar = 0;
  269. }
  270. // Normalize the string, stripping out redundant dots, spaces, and slashes.
  271. while (index < path.Length)
  272. {
  273. var currentChar = path[index];
  274. // We handle both directory separators and dots specially. For
  275. // directory separators, we consume consecutive appearances.
  276. // For dots, we consume all dots beyond the second in
  277. // succession. All other characters are added as is. In
  278. // addition we consume all spaces after the last other char
  279. // in a directory name up until the directory separator.
  280. if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar)
  281. {
  282. // If we have a path like "123.../foo", remove the trailing dots.
  283. // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
  284. // Also remove trailing spaces from both files & directory names.
  285. // This was agreed on with the OS team to fix undeletable directory
  286. // names ending in spaces.
  287. // If we saw a '\' as the previous last significant character and
  288. // are simply going to write out dots, suppress them.
  289. // If we only contain dots and slashes though, only allow
  290. // a string like [dot]+ [space]*. Ignore everything else.
  291. // Legal: "\.. \", "\...\", "\. \"
  292. // Illegal: "\.. .\", "\. .\", "\ .\"
  293. if (numSigChars == 0)
  294. {
  295. // Dot and space handling
  296. if (numDots > 0)
  297. {
  298. // Look for ".[space]*" or "..[space]*"
  299. var start = lastSigChar + 1;
  300. if (path[start] != CurrentDirectoryPrefixChar)
  301. throw new ArgumentException(path);
  302. // Only allow "[dot]+[space]*", and normalize the
  303. // legal ones to "." or ".."
  304. if (numDots >= 2)
  305. {
  306. // Reject "C:..."
  307. if (startedWithVolumeSeparator && numDots > 2)
  308. throw new ArgumentException(path);
  309. if (path[start + 1] == CurrentDirectoryPrefixChar)
  310. {
  311. // Search for a space in the middle of the dots and throw
  312. for (var i = start + 2; i < start + numDots; i++)
  313. {
  314. if (path[i] != CurrentDirectoryPrefixChar)
  315. throw new ArgumentException(path);
  316. }
  317. numDots = 2;
  318. }
  319. else
  320. {
  321. if (numDots > 1)
  322. throw new ArgumentException(path);
  323. numDots = 1;
  324. }
  325. }
  326. if (numDots == 2)
  327. newBuffer.Append(CurrentDirectoryPrefixChar);
  328. newBuffer.Append(CurrentDirectoryPrefixChar);
  329. fixupDirectorySeparator = false;
  330. // Continue in this case, potentially writing out '\'.
  331. }
  332. if (numSpaces > 0 && firstSegment)
  333. {
  334. // Handle strings like " \\server\share".
  335. if (index + 1 < path.Length && (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar))
  336. newBuffer.Append(DirectorySeparatorChar);
  337. }
  338. }
  339. numDots = 0;
  340. numSpaces = 0; // Suppress trailing spaces
  341. if (!fixupDirectorySeparator)
  342. {
  343. fixupDirectorySeparator = true;
  344. newBuffer.Append(DirectorySeparatorChar);
  345. }
  346. numSigChars = 0;
  347. lastSigChar = index;
  348. startedWithVolumeSeparator = false;
  349. firstSegment = false;
  350. var thisPos = newBuffer.Length - 1;
  351. if (thisPos - lastDirectorySeparatorPos > NativeMethods.MaxDirectoryLength)
  352. throw new PathTooLongException(path);
  353. lastDirectorySeparatorPos = thisPos;
  354. } // if (Found directory separator)
  355. else if (currentChar == CurrentDirectoryPrefixChar)
  356. {
  357. // Reduce only multiple .'s only after slash to 2 dots. For
  358. // instance a...b is a valid file name.
  359. numDots++;
  360. // Don't flush out non-terminal spaces here, because they may in
  361. // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
  362. // which is the conclusion of removing trailing dots & spaces,
  363. // as well as folding multiple '\' characters.
  364. }
  365. else if (currentChar == ' ')
  366. numSpaces++;
  367. else
  368. { // Normal character logic
  369. fixupDirectorySeparator = false;
  370. // To reject strings like "C:...\foo" and "C :\foo"
  371. if (firstSegment && currentChar == VolumeSeparatorChar)
  372. {
  373. // Only accept "C:", not "c :" or ":"
  374. // Get a drive letter or ' ' if index is 0.
  375. var driveLetter = (index > 0) ? path[index - 1] : ' ';
  376. var validPath = (numDots == 0) && (numSigChars >= 1) && (driveLetter != ' ');
  377. if (!validPath)
  378. throw new ArgumentException(path);
  379. startedWithVolumeSeparator = true;
  380. // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
  381. if (numSigChars > 1)
  382. {
  383. // Common case, simply do nothing
  384. var spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
  385. while ((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ')
  386. spaceCount++;
  387. if (numSigChars - spaceCount == 1)
  388. {
  389. //Safe to update stack ptr directly
  390. newBuffer.Length = 0;
  391. newBuffer.Append(driveLetter);
  392. // Overwrite spaces, we need a special case to not break " foo" as a relative path.
  393. }
  394. }
  395. numSigChars = 0;
  396. }
  397. else
  398. numSigChars += 1 + numDots + numSpaces;
  399. // Copy any spaces & dots since the last significant character
  400. // to here. Note we only counted the number of dots & spaces,
  401. // and don't know what order they're in. Hence the copy.
  402. if (numDots > 0 || numSpaces > 0)
  403. {
  404. var numCharsToCopy = lastSigChar >= 0 ? index - lastSigChar - 1 : index;
  405. if (numCharsToCopy > 0)
  406. for (var i = 0; i < numCharsToCopy; i++)
  407. newBuffer.Append(path[lastSigChar + 1 + i]);
  408. numDots = 0;
  409. numSpaces = 0;
  410. }
  411. newBuffer.Append(currentChar);
  412. lastSigChar = index;
  413. }
  414. index++;
  415. }
  416. if (newBuffer.Length - 1 - lastDirectorySeparatorPos > NativeMethods.MaxDirectoryLength)
  417. throw new PathTooLongException(path);
  418. // Drop any trailing dots and spaces from file & directory names, EXCEPT
  419. // we MUST make sure that "C:\foo\.." is correctly handled.
  420. // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
  421. if (numSigChars == 0)
  422. {
  423. if (numDots > 0)
  424. {
  425. // Look for ".[space]*" or "..[space]*"
  426. var start = lastSigChar + 1;
  427. if (path[start] != CurrentDirectoryPrefixChar)
  428. throw new ArgumentException(path);
  429. // Only allow "[dot]+[space]*", and normalize the legal ones to "." or ".."
  430. if (numDots >= 2)
  431. {
  432. // Reject "C:..."
  433. if (startedWithVolumeSeparator && numDots > 2)
  434. throw new ArgumentException(path);
  435. if (path[start + 1] == CurrentDirectoryPrefixChar)
  436. {
  437. // Search for a space in the middle of the dots and throw
  438. for (var i = start + 2; i < start + numDots; i++)
  439. if (path[i] != CurrentDirectoryPrefixChar)
  440. throw new ArgumentException(path);
  441. numDots = 2;
  442. }
  443. else
  444. {
  445. if (numDots > 1)
  446. throw new ArgumentException(path);
  447. numDots = 1;
  448. }
  449. }
  450. if (numDots == 2)
  451. newBuffer.Append(CurrentDirectoryPrefixChar);
  452. newBuffer.Append(CurrentDirectoryPrefixChar);
  453. }
  454. }
  455. // If we ended up eating all the characters, bail out.
  456. if (newBuffer.Length == 0)
  457. throw new ArgumentException(path);
  458. // Disallow URL's here. Some of our other Win32 API calls will reject
  459. // them later, so we might be better off rejecting them here.
  460. // Note we've probably turned them into "file:\D:\foo.tmp" by now.
  461. // But for compatibility, ensure that callers that aren't doing a
  462. // full check aren't rejected here.
  463. if ((options & GetFullPathOptions.FullCheck) != 0)
  464. {
  465. var newBufferString = newBuffer.ToString();
  466. if (newBufferString.StartsWith("http:", StringComparison.OrdinalIgnoreCase) || newBufferString.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
  467. throw new ArgumentException(path);
  468. }
  469. // Call the Win32 API to do the final canonicalization step.
  470. var result = 1;
  471. if (result != 0)
  472. {
  473. /* Throw an ArgumentException for paths like \\, \\server, \\server\
  474. This check can only be properly done after normalizing, so
  475. \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\
  476. (an internal kernel path) because it provides aliases for drives. */
  477. if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\')
  478. {
  479. var startIndex = 2;
  480. while (startIndex < result)
  481. {
  482. if (newBuffer[startIndex] == '\\')
  483. {
  484. startIndex++;
  485. break;
  486. }
  487. startIndex++;
  488. }
  489. if (startIndex == result)
  490. throw new ArgumentException(path);
  491. }
  492. }
  493. return newBuffer.ToString();
  494. }
  495. #endregion // Internal Methods
  496. }
  497. }