PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Castle.Services.Transaction/IO/PathInfo.cs

https://github.com/gusgorman/Castle.Services.Transaction
C# | 395 lines | 248 code | 39 blank | 108 comment | 28 complexity | 89ab93344d4f62efe20c04efba6c98fa MD5 | raw file
  1. #region License
  2. // Copyright 2004-2010 Castle Project - http://www.castleproject.org/
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #endregion
  17. namespace Castle.Services.Transaction.IO
  18. {
  19. using System;
  20. using System.Net;
  21. using System.Text.RegularExpressions;
  22. /// <summary>
  23. /// Path data holder.
  24. /// Invariant: no fields nor properties are null after c'tor.
  25. /// </summary>
  26. public struct PathInfo
  27. {
  28. private const string _StrRegex =
  29. @"(?<root>
  30. (?<UNC_prefix> \\\\\?\\ (?<UNC_literal>UNC\\)? )?
  31. (?<options>
  32. (?:
  33. (?<drive>(?<drive_letter>[A-Z]{1,3}):
  34. )\\
  35. )
  36. |(?<server>(?(UNC_prefix)|\\\\) #this is optional IIF we have the UNC_prefix, so only match \\ if we did not have it
  37. (?:
  38. (?<ipv4>(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})
  39. |(?:\[(?<ipv6>[A-Fa-f0-9:]{3,39})\])
  40. |(?<server_name>[\w\-]+) #allow dashes in server names
  41. )\\
  42. )
  43. |(?<device>
  44. (?<dev_prefix>\\\\\.\\)
  45. ((?<dev_name>[\w\-]+)
  46. |(?<dev_guid>\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\})
  47. )\\
  48. )
  49. |/
  50. |\\ # we can also refer to the current drive alone
  51. )
  52. )?
  53. (?<nonrootpath>
  54. (?!\\)
  55. (?<rel_drive>\w{1,3}:)?
  56. (?<folders_files>.+))?";
  57. private static Regex _Regex;
  58. static PathInfo()
  59. {
  60. _Regex = new Regex(_StrRegex,
  61. RegexOptions.Compiled |
  62. RegexOptions.IgnorePatternWhitespace |
  63. RegexOptions.IgnoreCase |
  64. RegexOptions.Multiline);
  65. }
  66. private string _Root,
  67. _UNCPrefix,
  68. _UNCLiteral,
  69. _Options,
  70. _Drive,
  71. _DriveLetter,
  72. _Server,
  73. _IPv4,
  74. _IPv6,
  75. _ServerName,
  76. _Device,
  77. _DevicePrefix,
  78. _DeviceName,
  79. _DeviceGuid,
  80. _NonRootPath,
  81. _RelDrive,
  82. _FolderAndFiles;
  83. public static PathInfo Parse(string path)
  84. {
  85. if (path == null) throw new ArgumentNullException("path");
  86. var matches = _Regex.Matches(path);
  87. Func<string, string> m = s => GetMatch(matches, s);
  88. // this might be possible to improve using raw indicies (ints) instead.
  89. return new PathInfo(
  90. m("root"),
  91. m("UNC_prefix"),
  92. m("UNC_literal"),
  93. m("options"),
  94. m("drive"),
  95. m("drive_letter"),
  96. m("server"),
  97. m("ipv4"),
  98. m("ipv6"),
  99. m("server_name"),
  100. m("device"),
  101. m("dev_prefix"),
  102. m("dev_name"),
  103. m("dev_guid"),
  104. m("nonrootpath"),
  105. m("rel_drive"),
  106. m("folders_files")
  107. );
  108. }
  109. private static string GetMatch(MatchCollection matches,
  110. string groupIndex)
  111. {
  112. var matchC = matches.Count;
  113. for (int i = 0; i < matchC; i++)
  114. {
  115. if (matches[i].Groups[groupIndex].Success)
  116. return matches[i].Groups[groupIndex].Value;
  117. }
  118. return string.Empty;
  119. }
  120. private PathInfo(string root, string uncPrefix, string uncLiteral, string options, string drive, string driveLetter, string server, string iPv4, string iPv6, string serverName, string device, string devicePrefix, string deviceName, string deviceGuid, string nonRootPath, string relDrive, string folderAndFiles)
  121. {
  122. _Root = root;
  123. _UNCPrefix = uncPrefix;
  124. _UNCLiteral = uncLiteral;
  125. _Options = options;
  126. _Drive = drive;
  127. _DriveLetter = driveLetter;
  128. _Server = server;
  129. _IPv4 = iPv4;
  130. _IPv6 = iPv6;
  131. _ServerName = serverName;
  132. _Device = device;
  133. _DevicePrefix = devicePrefix;
  134. _DeviceName = deviceName;
  135. _DeviceGuid = deviceGuid;
  136. _NonRootPath = nonRootPath;
  137. _RelDrive = relDrive;
  138. _FolderAndFiles = folderAndFiles;
  139. }
  140. /// <summary>
  141. /// Examples of return values:
  142. /// <list>
  143. /// <item>\\?\UNC\C:\</item>
  144. /// <item>\\?\UNC\servername\</item>
  145. /// <item>\\192.168.0.2\</item>
  146. /// <item>C:\</item>
  147. /// </list>
  148. ///
  149. /// Definition: Returns part of the string that is in itself uniquely from the currently
  150. /// executing CLR.
  151. /// </summary>
  152. public string Root
  153. {
  154. get { return _Root; }
  155. }
  156. /// <summary>
  157. /// Examples of return values:
  158. /// <list>
  159. /// <item></item>
  160. /// </list>
  161. /// </summary>
  162. public string UNCPrefix
  163. {
  164. get { return _UNCPrefix; }
  165. }
  166. /// <summary>
  167. ///
  168. /// </summary>
  169. public string UNCLiteral
  170. {
  171. get { return _UNCLiteral; }
  172. }
  173. /// <summary>
  174. ///
  175. /// </summary>
  176. public string Options
  177. {
  178. get { return _Options; }
  179. }
  180. /// <summary>
  181. ///
  182. /// </summary>
  183. public string Drive
  184. {
  185. get { return _Drive; }
  186. }
  187. /// <summary>
  188. ///
  189. /// </summary>
  190. public string DriveLetter
  191. {
  192. get { return _DriveLetter; }
  193. }
  194. /// <summary>
  195. ///
  196. /// </summary>
  197. public string Server
  198. {
  199. get { return _Server; }
  200. }
  201. /// <summary>
  202. /// Gets the 0.0.0.0-based IP-address if any.
  203. /// </summary>
  204. public string IPv4
  205. {
  206. get { return _IPv4; }
  207. }
  208. /// <summary>
  209. ///
  210. /// </summary>
  211. public string IPv6
  212. {
  213. get { return _IPv6; }
  214. }
  215. /// <summary>
  216. ///
  217. /// </summary>
  218. public string ServerName
  219. {
  220. get { return _ServerName; }
  221. }
  222. /// <summary>
  223. ///
  224. /// </summary>
  225. public string Device
  226. {
  227. get { return _Device; }
  228. }
  229. /// <summary>
  230. ///
  231. /// </summary>
  232. public string DevicePrefix
  233. {
  234. get { return _DevicePrefix; }
  235. }
  236. /// <summary>
  237. ///
  238. /// </summary>
  239. public string DeviceName
  240. {
  241. get { return _DeviceName; }
  242. }
  243. /// <summary>
  244. /// Gets the device GUID in the form
  245. /// <code>{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</code>
  246. /// i.e. 8-4-4-4-12 hex digits with curly brackets.
  247. /// </summary>
  248. public string DeviceGuid
  249. {
  250. get { return _DeviceGuid; }
  251. }
  252. /// <summary>
  253. /// Gets a the part of the path that starts when the root ends.
  254. /// The root in turn is any UNC-prefix plus device, drive, server or ip-prefix.
  255. /// This string may not start with neither of '\' or '/'.
  256. /// </summary>
  257. public string NonRootPath
  258. {
  259. get { return _NonRootPath; }
  260. }
  261. /// <summary>
  262. ///
  263. /// </summary>
  264. public string RelDrive
  265. {
  266. get { return _RelDrive; }
  267. }
  268. /// <summary>
  269. /// The only time when this differs from <see cref="NonRootPath"/>
  270. /// is when a path like this is used:
  271. /// <code>C:../parent/a.txt</code>, otherwise, for all paths,
  272. /// this property equals <see cref="NonRootPath"/>.
  273. /// </summary>
  274. public string FolderAndFiles
  275. {
  276. get { return _FolderAndFiles; }
  277. }
  278. public PathType Type
  279. {
  280. get
  281. {
  282. if (Device != string.Empty)
  283. return PathType.Device;
  284. if (ServerName != string.Empty)
  285. return PathType.Server;
  286. if (IPv4 != string.Empty)
  287. return PathType.IPv4;
  288. if (IPv6 != string.Empty)
  289. return PathType.IPv6;
  290. if (Drive != string.Empty)
  291. return PathType.Drive;
  292. return PathType.Relative;
  293. }
  294. }
  295. /// <summary>
  296. /// Returns whether <see cref="Root"/> is not an empty string.
  297. /// </summary>
  298. public bool IsRooted
  299. {
  300. get { return _Root != string.Empty; }
  301. }
  302. /// <summary>
  303. /// Returns whether the current PathInfo is a valid parent of the child path info
  304. /// passed as argument.
  305. /// </summary>
  306. /// <param name="child">The path info to verify</param>
  307. /// <returns>Whether it is true that the current path info is a parent of child.</returns>
  308. /// <exception cref="NotSupportedException">If this instance of path info and child aren't rooted.</exception>
  309. public bool IsParentOf(PathInfo child)
  310. {
  311. if (Root == string.Empty || child.Root == string.Empty)
  312. throw new NotSupportedException("Non-rooted paths are not supported.");
  313. var OK = child.FolderAndFiles.StartsWith(FolderAndFiles);
  314. switch (Type)
  315. {
  316. case PathType.Device:
  317. OK &= child.DeviceName.ToLowerInvariant() == DeviceName.ToLowerInvariant();
  318. break;
  319. case PathType.Server:
  320. OK &= child.ServerName.ToLowerInvariant() == ServerName.ToLowerInvariant();
  321. break;
  322. case PathType.IPv4:
  323. OK &= IPAddress.Parse(child.IPv4).Equals(IPAddress.Parse(IPv4));
  324. break;
  325. case PathType.IPv6:
  326. OK &= (IPAddress.Parse(child.IPv6).Equals(IPAddress.Parse(IPv6)));
  327. break;
  328. case PathType.Relative:
  329. throw new InvalidOperationException("Since root isn't empty we should never get relative paths.");
  330. case PathType.Drive:
  331. OK &= DriveLetter.ToLowerInvariant() == child.DriveLetter.ToLowerInvariant();
  332. break;
  333. }
  334. return OK;
  335. }
  336. /// <summary>
  337. /// Removes the path info passes as a parameter from the current root. Only works for two rooted paths with same root.
  338. /// Does NOT cover all edge cases, please verify its intended results yourself.
  339. /// <example>
  340. ///
  341. /// </example>
  342. /// </summary>
  343. /// <param name="other"></param>
  344. /// <returns></returns>
  345. public string RemoveParameterFromRoot(PathInfo other)
  346. {
  347. if (Root != other.Root)
  348. throw new InvalidOperationException("Roots of this and other don't match.");
  349. if (other.FolderAndFiles.Length > FolderAndFiles.Length)
  350. throw new InvalidOperationException(
  351. "The folders and files part of the second parameter must be shorter than that path you wish to subtract from.");
  352. if (other.FolderAndFiles == FolderAndFiles) return string.Empty;
  353. return FolderAndFiles.Substring(other.FolderAndFiles.Length).TrimStart(Path.GetDirectorySeparatorChars());
  354. }
  355. }
  356. }