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.
 
 

306 lines
18 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.Security;
  23. using System.Text;
  24. namespace Alphaleonis.Win32.Filesystem
  25. {
  26. public static partial class Path
  27. {
  28. #region .NET
  29. /// <summary>Returns the absolute path for the specified path string.</summary>
  30. /// <returns>The fully qualified location of path, such as "C:\MyFile.txt".</returns>
  31. /// <remarks>
  32. /// <para>GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file.</para>
  33. /// <para>It also calculates the address of the file name portion of the full path and file name.</para>
  34. /// <para>&#160;</para>
  35. /// <para>This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.</para>
  36. /// <para>The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0".</para>
  37. /// <para>&#160;</para>
  38. /// <para>MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and</para>
  39. /// <para>should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process,</para>
  40. /// <para>therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value.</para>
  41. /// <para>This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread,</para>
  42. /// <para>for example parsing file names from the command line argument string in the main thread prior to creating any additional threads.</para>
  43. /// <para>Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported.</para>
  44. /// </remarks>
  45. /// <exception cref="ArgumentNullException"/>
  46. /// <exception cref="ArgumentException"/>
  47. /// <exception cref="NotSupportedException"/>
  48. /// <param name="path">The file or directory for which to obtain absolute path information.</param>
  49. [SecurityCritical]
  50. public static string GetFullPath(string path)
  51. {
  52. return GetFullPathTackleCore(null, path, GetFullPathOptions.None);
  53. }
  54. #endregion // .NET
  55. #region AlphaFS
  56. /// <summary>Returns the absolute path for the specified path string.</summary>
  57. /// <returns>The fully qualified location of path, such as "C:\MyFile.txt".</returns>
  58. /// <remarks>
  59. /// <para>GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file.</para>
  60. /// <para>It also calculates the address of the file name portion of the full path and file name.</para>
  61. /// <para>&#160;</para>
  62. /// <para>This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.</para>
  63. /// <para>The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0".</para>
  64. /// <para>&#160;</para>
  65. /// <para>MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and</para>
  66. /// <para>should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process,</para>
  67. /// <para>therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value.</para>
  68. /// <para>This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread,</para>
  69. /// <para>for example parsing file names from the command line argument string in the main thread prior to creating any additional threads.</para>
  70. /// <para>Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported.</para>
  71. /// </remarks>
  72. /// <exception cref="ArgumentNullException"/>
  73. /// <exception cref="ArgumentException"/>
  74. /// <exception cref="NotSupportedException"/>
  75. /// <param name="path">The file or directory for which to obtain absolute path information.</param>
  76. /// <param name="options">Options for controlling the full path retrieval.</param>
  77. [SecurityCritical]
  78. public static string GetFullPath(string path, GetFullPathOptions options)
  79. {
  80. return GetFullPathTackleCore(null, path, options);
  81. }
  82. /// <summary>[AlphaFS] Returns the absolute path for the specified path string.</summary>
  83. /// <returns>The fully qualified location of path, such as "C:\MyFile.txt".</returns>
  84. /// <remarks>
  85. /// <para>GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file.</para>
  86. /// <para>It also calculates the address of the file name portion of the full path and file name.</para>
  87. /// <para>&#160;</para>
  88. /// <para>This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.</para>
  89. /// <para>The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0".</para>
  90. /// <para>&#160;</para>
  91. /// <para>MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and</para>
  92. /// <para>should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process,</para>
  93. /// <para>therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value.</para>
  94. /// <para>This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread,</para>
  95. /// <para>for example parsing file names from the command line argument string in the main thread prior to creating any additional threads.</para>
  96. /// <para>Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported.</para>
  97. /// </remarks>
  98. /// <exception cref="ArgumentException"/>
  99. /// <exception cref="ArgumentNullException"/>
  100. /// <exception cref="NotSupportedException"/>
  101. /// <param name="transaction">The transaction.</param>
  102. /// <param name="path">The file or directory for which to obtain absolute path information.</param>
  103. [SecurityCritical]
  104. public static string GetFullPathTransacted(KernelTransaction transaction, string path)
  105. {
  106. return GetFullPathTackleCore(transaction, path, GetFullPathOptions.None);
  107. }
  108. /// <summary>[AlphaFS] Returns the absolute path for the specified path string.</summary>
  109. /// <returns>The fully qualified location of path, such as "C:\MyFile.txt".</returns>
  110. /// <remarks>
  111. /// <para>GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file.</para>
  112. /// <para>It also calculates the address of the file name portion of the full path and file name.</para>
  113. /// <para>&#160;</para>
  114. /// <para>This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.</para>
  115. /// <para>The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0".</para>
  116. /// <para>&#160;</para>
  117. /// <para>MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and</para>
  118. /// <para>should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process,</para>
  119. /// <para>therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value.</para>
  120. /// <para>This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread,</para>
  121. /// <para>for example parsing file names from the command line argument string in the main thread prior to creating any additional threads.</para>
  122. /// <para>Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported.</para>
  123. /// </remarks>
  124. /// <exception cref="ArgumentException"/>
  125. /// <exception cref="ArgumentNullException"/>
  126. /// <exception cref="NotSupportedException"/>
  127. /// <param name="transaction">The transaction.</param>
  128. /// <param name="path">The file or directory for which to obtain absolute path information.</param>
  129. /// <param name="options">Options for controlling the full path retrieval.</param>
  130. [SecurityCritical]
  131. public static string GetFullPathTransacted(KernelTransaction transaction, string path, GetFullPathOptions options)
  132. {
  133. return GetFullPathTackleCore(transaction, path, options);
  134. }
  135. #endregion // AlphaFS
  136. #region Internal Methods
  137. /// <summary>Retrieves the absolute path for the specified <paramref name="path"/> string.</summary>
  138. /// <returns>The fully qualified location of <paramref name="path"/>, such as "C:\MyFile.txt".</returns>
  139. /// <remarks>
  140. /// <para>GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file.</para>
  141. /// <para>It also calculates the address of the file name portion of the full path and file name.</para>
  142. /// <para>&#160;</para>
  143. /// <para>This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.</para>
  144. /// <para>The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0".</para>
  145. /// <para>&#160;</para>
  146. /// <para>MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and</para>
  147. /// <para>should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process,</para>
  148. /// <para>therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value.</para>
  149. /// <para>This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread,</para>
  150. /// <para>for example parsing file names from the command line argument string in the main thread prior to creating any additional threads.</para>
  151. /// <para>Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported.</para>
  152. /// </remarks>
  153. /// <exception cref="ArgumentException"/>
  154. /// <exception cref="ArgumentNullException"/>
  155. /// <param name="transaction">The transaction.</param>
  156. /// <param name="path">The file or directory for which to obtain absolute path information.</param>
  157. /// <param name="options">Options for controlling the full path retrieval.</param>
  158. [SecurityCritical]
  159. internal static string GetFullPathCore(KernelTransaction transaction, string path, GetFullPathOptions options)
  160. {
  161. if (path != null)
  162. if (path.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase) ||path.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase))
  163. return path;
  164. if (options != GetFullPathOptions.None)
  165. {
  166. if ((options & GetFullPathOptions.CheckInvalidPathChars) != 0)
  167. {
  168. var checkAdditional = (options & GetFullPathOptions.CheckAdditional) != 0;
  169. CheckInvalidPathChars(path, checkAdditional, false);
  170. // Prevent duplicate checks.
  171. options &= ~GetFullPathOptions.CheckInvalidPathChars;
  172. if (checkAdditional)
  173. options &= ~GetFullPathOptions.CheckAdditional;
  174. }
  175. // Do not remove trailing directory separator when path points to a drive like: "C:\"
  176. // Doing so makes path point to the current directory.
  177. if (path == null || path.Length <= 3 || (!path.StartsWith(LongPathPrefix, StringComparison.OrdinalIgnoreCase) && path[1] != VolumeSeparatorChar))
  178. options &= ~GetFullPathOptions.RemoveTrailingDirectorySeparator;
  179. }
  180. var pathLp = GetLongPathCore(path, options);
  181. uint bufferSize = NativeMethods.MaxPathUnicode;
  182. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  183. {
  184. startGetFullPathName:
  185. var buffer = new StringBuilder((int)bufferSize);
  186. var returnLength = (transaction == null || !NativeMethods.IsAtLeastWindowsVista
  187. // GetFullPathName() / GetFullPathNameTransacted()
  188. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  189. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  190. // 2013-04-15: MSDN confirms LongPath usage.
  191. ? NativeMethods.GetFullPathName(pathLp, bufferSize, buffer, IntPtr.Zero)
  192. : NativeMethods.GetFullPathNameTransacted(pathLp, bufferSize, buffer, IntPtr.Zero, transaction.SafeHandle));
  193. if (returnLength != Win32Errors.NO_ERROR)
  194. {
  195. if (returnLength > bufferSize)
  196. {
  197. bufferSize = returnLength;
  198. goto startGetFullPathName;
  199. }
  200. }
  201. else
  202. {
  203. if ((options & GetFullPathOptions.ContinueOnNonExist) != 0)
  204. return null;
  205. NativeError.ThrowException(pathLp);
  206. }
  207. var finalFullPath = (options & GetFullPathOptions.AsLongPath) != 0
  208. ? GetLongPathCore(buffer.ToString(), GetFullPathOptions.None)
  209. : GetRegularPathCore(buffer.ToString(), GetFullPathOptions.None, false);
  210. finalFullPath = NormalizePath(finalFullPath, options);
  211. if ((options & GetFullPathOptions.KeepDotOrSpace) != 0)
  212. {
  213. if (pathLp.EndsWith(".", StringComparison.OrdinalIgnoreCase))
  214. finalFullPath += ".";
  215. var lastChar = pathLp[pathLp.Length - 1];
  216. if (char.IsWhiteSpace(lastChar))
  217. finalFullPath += lastChar;
  218. }
  219. return finalFullPath;
  220. }
  221. }
  222. private static string GetFullPathTackleCore(KernelTransaction transaction, string path, GetFullPathOptions options)
  223. {
  224. if (path != null)
  225. {
  226. if (path.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase) || path.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase))
  227. return path;
  228. CheckInvalidUncPath(path);
  229. }
  230. CheckSupportedPathFormat(path, true, true);
  231. return GetFullPathCore(transaction, path, options);
  232. }
  233. /// <summary>Applies the <seealso cref="GetFullPathOptions"/> to <paramref name="path"/></summary>
  234. /// <returns><paramref name="path"/> with applied <paramref name="options"/>.</returns>
  235. /// <exception cref="ArgumentNullException"/>
  236. /// <exception cref="ArgumentException"/>
  237. /// <param name="path"></param>
  238. /// <param name="options"></param>
  239. private static string ApplyFullPathOptions(string path, GetFullPathOptions options)
  240. {
  241. if ((options & GetFullPathOptions.TrimEnd) != 0)
  242. if ((options & GetFullPathOptions.KeepDotOrSpace) == 0)
  243. path = path.TrimEnd();
  244. if ((options & GetFullPathOptions.AddTrailingDirectorySeparator) != 0)
  245. path = AddTrailingDirectorySeparator(path, false);
  246. if ((options & GetFullPathOptions.RemoveTrailingDirectorySeparator) != 0)
  247. path = RemoveTrailingDirectorySeparator(path, false);
  248. if ((options & GetFullPathOptions.CheckInvalidPathChars) != 0)
  249. CheckInvalidPathChars(path, (options & GetFullPathOptions.CheckAdditional) != 0, false);
  250. // Trim leading whitespace.
  251. if ((options & GetFullPathOptions.KeepDotOrSpace) == 0)
  252. path = path.TrimStart();
  253. return path;
  254. }
  255. #endregion // Internal Methods
  256. }
  257. }