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.
 
 

274 lines
14 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.Security;
  24. using System.Text;
  25. namespace Alphaleonis.Win32.Filesystem
  26. {
  27. public static partial class Path
  28. {
  29. #region GetLongPath
  30. /// <summary>Makes an extended long path from the specified <paramref name="path"/> by prefixing <see cref="LongPathPrefix"/>.</summary>
  31. /// <returns>The <paramref name="path"/> prefixed with a <see cref="LongPathPrefix"/>, the minimum required full path is: "C:\".</returns>
  32. /// <remarks>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.</remarks>
  33. /// <exception cref="ArgumentNullException"/>
  34. /// <exception cref="ArgumentException"/>
  35. /// <param name="path">The path to the file or directory, this can also be an UNC path.</param>
  36. [SecurityCritical]
  37. public static string GetLongPath(string path)
  38. {
  39. return GetLongPathCore(path, GetFullPathOptions.None);
  40. }
  41. #endregion // GetLongPath
  42. #region GetLongFrom83ShortPath
  43. /// <summary>[AlphaFS] Converts the specified existing path to its regular long form.</summary>
  44. /// <param name="path">An existing path to a folder or file.</param>
  45. /// <returns>The regular full path.</returns>
  46. [SecurityCritical]
  47. public static string GetLongFrom83ShortPath(string path)
  48. {
  49. return GetLongShort83PathCore(null, path, false);
  50. }
  51. /// <summary>[AlphaFS] Converts the specified existing path to its regular long form.</summary>
  52. /// <param name="transaction">The transaction.</param>
  53. /// <param name="path">An existing path to a folder or file.</param>
  54. /// <returns>The regular full path.</returns>
  55. [SecurityCritical]
  56. public static string GetLongFrom83ShortPathTransacted(KernelTransaction transaction, string path)
  57. {
  58. return GetLongShort83PathCore(transaction, path, false);
  59. }
  60. #endregion // GetLongFrom83ShortPath
  61. #region GetRegularPath
  62. /// <summary>[AlphaFS] Gets the regular path from long prefixed one. i.e.: "\\?\C:\Temp\file.txt" to C:\Temp\file.txt" or: "\\?\UNC\Server\share\file.txt" to "\\Server\share\file.txt".</summary>
  63. /// <returns>Regular form path string.</returns>
  64. /// <remarks>This method does not handle paths with volume names, eg. \\?\Volume{GUID}\Folder\file.txt.</remarks>
  65. /// <param name="path">The path.</param>
  66. [SecurityCritical]
  67. public static string GetRegularPath(string path)
  68. {
  69. return GetRegularPathCore(path, GetFullPathOptions.CheckInvalidPathChars, false);
  70. }
  71. #endregion // GetRegularPath
  72. #region GetShort83Path
  73. /// <summary>[AlphaFS] Retrieves the short path form of the specified path.</summary>
  74. /// <returns>A path that has the 8.3 path form.</returns>
  75. /// <remarks>Will fail on NTFS volumes with disabled 8.3 name generation.</remarks>
  76. /// <remarks>The path must actually exist to be able to get the short path name.</remarks>
  77. /// <param name="path">An existing path to a folder or file.</param>
  78. [SecurityCritical]
  79. public static string GetShort83Path(string path)
  80. {
  81. return GetLongShort83PathCore(null, path, true);
  82. }
  83. /// <summary>[AlphaFS] Retrieves the short path form of the specified path.</summary>
  84. /// <returns>A path that has the 8.3 path form.</returns>
  85. /// <remarks>Will fail on NTFS volumes with disabled 8.3 name generation.</remarks>
  86. /// <remarks>The path must actually exist to be able to get the short path name.</remarks>
  87. /// <param name="transaction">The transaction.</param>
  88. /// <param name="path">An existing path to a folder or file.</param>
  89. [SecurityCritical]
  90. public static string GetShort83PathTransacted(KernelTransaction transaction, string path)
  91. {
  92. return GetLongShort83PathCore(transaction, path, true);
  93. }
  94. #endregion // GetShort83Path
  95. #region IsLongPath
  96. /// <summary>[AlphaFS] Determines whether the specified path starts with a <see cref="LongPathPrefix"/> or <see cref="LongPathUncPrefix"/>.</summary>
  97. /// <returns><see langword="true"/> if the specified path has a long path (UNC) prefix, <see langword="false"/> otherwise.</returns>
  98. /// <param name="path">The path to the file or directory.</param>
  99. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Utils.IsNullOrWhiteSpace validates arguments.")]
  100. [SecurityCritical]
  101. public static bool IsLongPath(string path)
  102. {
  103. return !Utils.IsNullOrWhiteSpace(path) && path.StartsWith(LongPathPrefix, StringComparison.OrdinalIgnoreCase);
  104. }
  105. #endregion // IsLongPath
  106. #region Internals Methods
  107. /// <summary>Makes an extended long path from the specified <paramref name="path"/> by prefixing <see cref="LongPathPrefix"/>.</summary>
  108. /// <returns>The <paramref name="path"/> prefixed with a <see cref="LongPathPrefix"/>, the minimum required full path is: "C:\".</returns>
  109. /// <remarks>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.</remarks>
  110. /// <exception cref="ArgumentNullException"/>
  111. /// <exception cref="ArgumentException"/>
  112. /// <param name="path">The path to the file or directory, this can also be an UNC path.</param>
  113. /// <param name="options">Options for controlling the full path retrieval.</param>
  114. [SecurityCritical]
  115. internal static string GetLongPathCore(string path, GetFullPathOptions options)
  116. {
  117. if (path == null)
  118. throw new ArgumentNullException("path");
  119. if (path.Length == 0 || Utils.IsNullOrWhiteSpace(path))
  120. throw new ArgumentException(Resources.Path_Is_Zero_Length_Or_Only_White_Space, "path");
  121. if (options != GetFullPathOptions.None)
  122. path = ApplyFullPathOptions(path, options);
  123. // ".", "C:"
  124. if (path.Length <= 2 ||
  125. path.StartsWith(LongPathPrefix, StringComparison.OrdinalIgnoreCase) ||
  126. path.StartsWith(LogicalDrivePrefix, StringComparison.OrdinalIgnoreCase))
  127. return path;
  128. if (path.StartsWith(UncPrefix, StringComparison.OrdinalIgnoreCase))
  129. return LongPathUncPrefix + path.Substring(UncPrefix.Length);
  130. // Don't use char.IsLetter() here as that can be misleading.
  131. // The only valid drive letters are: a-z and A-Z.
  132. char c = path[0];
  133. return IsPathRooted(path, false) && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
  134. ? LongPathPrefix + path
  135. : path;
  136. }
  137. /// <summary>Retrieves the short path form, or the regular long form of the specified <paramref name="path"/>.</summary>
  138. /// <returns>If <paramref name="getShort"/> is <see langword="true"/>, a path of the 8.3 form otherwise the regular long form.</returns>
  139. /// <remarks>
  140. /// <para>Will fail on NTFS volumes with disabled 8.3 name generation.</para>
  141. /// <para>The path must actually exist to be able to get the short- or long path name.</para>
  142. /// </remarks>
  143. /// <param name="transaction">The transaction.</param>
  144. /// <param name="path">An existing path to a folder or file.</param>
  145. /// <param name="getShort"><see langword="true"/> to retrieve the short path form, <see langword="false"/> to retrieve the regular long form from the 8.3 <paramref name="path"/>.</param>
  146. [SecurityCritical]
  147. private static string GetLongShort83PathCore(KernelTransaction transaction, string path, bool getShort)
  148. {
  149. string pathLp = GetFullPathCore(transaction, path, GetFullPathOptions.AsLongPath | GetFullPathOptions.FullCheck);
  150. var buffer = new StringBuilder();
  151. uint actualLength = getShort ? NativeMethods.GetShortPathName(pathLp, null, 0) : (uint) path.Length;
  152. while (actualLength > buffer.Capacity)
  153. {
  154. buffer = new StringBuilder((int)actualLength);
  155. actualLength = getShort
  156. // GetShortPathName()
  157. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  158. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  159. // 2014-01-29: MSDN confirms LongPath usage.
  160. ? NativeMethods.GetShortPathName(pathLp, buffer, (uint)buffer.Capacity)
  161. : transaction == null || !NativeMethods.IsAtLeastWindowsVista
  162. // GetLongPathName()
  163. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  164. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  165. // 2014-01-29: MSDN confirms LongPath usage.
  166. ? NativeMethods.GetLongPathName(pathLp, buffer, (uint)buffer.Capacity)
  167. : NativeMethods.GetLongPathNameTransacted(pathLp, buffer, (uint)buffer.Capacity, transaction.SafeHandle);
  168. if (actualLength == Win32Errors.ERROR_SUCCESS)
  169. NativeError.ThrowException(pathLp);
  170. }
  171. return GetRegularPathCore(buffer.ToString(), GetFullPathOptions.None, false);
  172. }
  173. /// <summary>Gets the regular path from a long path.</summary>
  174. /// <returns>
  175. /// <para>Returns the regular form of a long <paramref name="path"/>.</para>
  176. /// <para>For example: "\\?\C:\Temp\file.txt" to: "C:\Temp\file.txt", or: "\\?\UNC\Server\share\file.txt" to: "\\Server\share\file.txt".</para>
  177. /// </returns>
  178. /// <remarks>
  179. /// MSDN: String.TrimEnd Method notes to Callers: http://msdn.microsoft.com/en-us/library/system.string.trimend%28v=vs.110%29.aspx
  180. /// </remarks>
  181. /// <exception cref="ArgumentNullException"/>
  182. /// <exception cref="ArgumentException"/>
  183. /// <param name="path">The path.</param>
  184. /// <param name="options">Options for controlling the full path retrieval.</param>
  185. /// <param name="allowEmpty">When <see langword="false"/>, throws an <see cref="ArgumentException"/>.</param>
  186. [SecurityCritical]
  187. internal static string GetRegularPathCore(string path, GetFullPathOptions options, bool allowEmpty)
  188. {
  189. if (path == null)
  190. throw new ArgumentNullException("path");
  191. if (!allowEmpty && (path.Length == 0 || Utils.IsNullOrWhiteSpace(path)))
  192. throw new ArgumentException(Resources.Path_Is_Zero_Length_Or_Only_White_Space, "path");
  193. if (options != GetFullPathOptions.None)
  194. path = ApplyFullPathOptions(path, options);
  195. return path.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase)
  196. || path.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase)
  197. || !path.StartsWith(LongPathPrefix, StringComparison.OrdinalIgnoreCase)
  198. ? path
  199. : (path.StartsWith(LongPathUncPrefix, StringComparison.OrdinalIgnoreCase)
  200. ? UncPrefix + path.Substring(LongPathUncPrefix.Length)
  201. : path.Substring(LongPathPrefix.Length));
  202. }
  203. /// <summary>Gets the path as a long full path.</summary>
  204. /// <returns>The path as an extended length path.</returns>
  205. /// <exception cref="ArgumentException"/>
  206. /// <param name="transaction">The transaction.</param>
  207. /// <param name="sourcePath">Full pathname of the source path to convert.</param>
  208. /// <param name="pathFormat">The path format to use.</param>
  209. /// <param name="options">Options for controlling the operation. Note that on .NET 3.5 the TrimEnd option has no effect.</param>
  210. internal static string GetExtendedLengthPathCore(KernelTransaction transaction, string sourcePath, PathFormat pathFormat, GetFullPathOptions options)
  211. {
  212. switch (pathFormat)
  213. {
  214. case PathFormat.LongFullPath:
  215. return sourcePath;
  216. case PathFormat.FullPath:
  217. return GetLongPathCore(sourcePath, GetFullPathOptions.None);
  218. case PathFormat.RelativePath:
  219. #if NET35
  220. // .NET 3.5 the TrimEnd option has no effect.
  221. options = options & ~GetFullPathOptions.TrimEnd;
  222. #endif
  223. return GetFullPathCore(transaction, sourcePath, GetFullPathOptions.AsLongPath | options);
  224. default:
  225. throw new ArgumentException("Invalid value for " + typeof(PathFormat).Name + ": " + pathFormat);
  226. }
  227. }
  228. #endregion // Internals Methods
  229. }
  230. }