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

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

https://github.com/martinernst/Castle.Transactions
C# | 456 lines | 317 code | 43 blank | 96 comment | 55 complexity | 72c2dd30df2c1db0e3300a9c1e73030f MD5 | raw file
  1. #region license
  2. // Copyright 2004-2011 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. #endregion
  16. using System;
  17. using System.Diagnostics.CodeAnalysis;
  18. using System.Diagnostics.Contracts;
  19. using System.Net;
  20. using System.Text.RegularExpressions;
  21. namespace Castle.Services.Transaction.IO
  22. {
  23. /// <summary>
  24. /// Path data holder.
  25. /// Invariant: no fields nor properties are null after c'tor.
  26. /// </summary>
  27. public struct PathInfo
  28. {
  29. private const string StrRegex =
  30. @"(?<root>
  31. (?<UNC_prefix> \\\\\?\\ (?<UNC_literal>UNC\\)? )?
  32. (?<options>
  33. (?:
  34. (?<drive>(?<drive_letter>[A-Z]{1,3}):
  35. )\\
  36. )
  37. |(?<server>(?(UNC_prefix)|\\\\) #this is optional IIF we have the UNC_prefix, so only match \\ if we did not have it
  38. (?:
  39. (?<ipv4>(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})
  40. |(?:\[(?<ipv6>[A-Fa-f0-9:]{3,39})\])
  41. |(?<server_name>[\w\-]+) #allow dashes in server names
  42. )\\
  43. )
  44. |(?<device>
  45. (?<dev_prefix>\\\\\.\\)
  46. ((?<dev_name>[\w\-]+)
  47. |(?<dev_guid>\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\})
  48. )\\
  49. )
  50. |/
  51. |\\ # we can also refer to the current drive alone
  52. )
  53. )?
  54. (?<nonrootpath>
  55. (?!\\)
  56. (?<rel_drive>\w{1,3}:)?
  57. (?<folders_files>.+))?";
  58. private static readonly Regex _Regex = new Regex(StrRegex,
  59. RegexOptions.Compiled |
  60. RegexOptions.IgnorePatternWhitespace |
  61. RegexOptions.IgnoreCase |
  62. RegexOptions.Multiline);
  63. private readonly string _Root,
  64. _UNCPrefix,
  65. _UNCLiteral,
  66. _Options,
  67. _Drive,
  68. _DriveLetter,
  69. _Server,
  70. _IPv4,
  71. _IPv6,
  72. _ServerName,
  73. _Device,
  74. _DevicePrefix,
  75. _DeviceName,
  76. _DeviceGuid,
  77. _NonRootPath,
  78. _RelDrive,
  79. _FolderAndFiles;
  80. // too stupid checker
  81. [ContractVerification(false)]
  82. public static PathInfo Parse(string path)
  83. {
  84. Contract.Requires(path != null);
  85. var matches = _Regex.Matches(path);
  86. Func<string, string> m = s => GetMatch(matches, s);
  87. // this might be possible to improve using raw indicies (ints) instead.
  88. return new PathInfo(
  89. m("root"),
  90. m("UNC_prefix"),
  91. m("UNC_literal"),
  92. m("options"),
  93. m("drive"),
  94. m("drive_letter"),
  95. m("server"),
  96. m("ipv4"),
  97. m("ipv6"),
  98. m("server_name"),
  99. m("device"),
  100. m("dev_prefix"),
  101. m("dev_name"),
  102. m("dev_guid"),
  103. m("nonrootpath"),
  104. m("rel_drive"),
  105. m("folders_files")
  106. );
  107. }
  108. private static string GetMatch(MatchCollection matches,
  109. string groupIndex)
  110. {
  111. Contract.Ensures(Contract.Result<string>() != null);
  112. var matchC = matches.Count;
  113. for (var 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. #region c'tor and non null invariants
  121. private PathInfo(string root, string uncPrefix, string uncLiteral, string options, string drive, string driveLetter,
  122. string server, string iPv4, string iPv6, string serverName, string device, string devicePrefix,
  123. string deviceName, string deviceGuid, string nonRootPath, string relDrive, string folderAndFiles)
  124. {
  125. Contract.Requires(root != null);
  126. Contract.Requires(uncPrefix != null);
  127. Contract.Requires(uncLiteral != null);
  128. Contract.Requires(options != null);
  129. Contract.Requires(drive != null);
  130. Contract.Requires(driveLetter != null);
  131. Contract.Requires(server != null);
  132. Contract.Requires(iPv4 != null);
  133. Contract.Requires(iPv6 != null);
  134. Contract.Requires(serverName != null);
  135. Contract.Requires(device != null);
  136. Contract.Requires(devicePrefix != null);
  137. Contract.Requires(deviceName != null);
  138. Contract.Requires(deviceGuid != null);
  139. Contract.Requires(nonRootPath != null);
  140. Contract.Requires(relDrive != null);
  141. Contract.Requires(folderAndFiles != null);
  142. _Root = root;
  143. _UNCPrefix = uncPrefix;
  144. _UNCLiteral = uncLiteral;
  145. _Options = options;
  146. _Drive = drive;
  147. _DriveLetter = driveLetter;
  148. _Server = server;
  149. _IPv4 = iPv4;
  150. _IPv6 = iPv6;
  151. _ServerName = serverName;
  152. _Device = device;
  153. _DevicePrefix = devicePrefix;
  154. _DeviceName = deviceName;
  155. _DeviceGuid = deviceGuid;
  156. _NonRootPath = nonRootPath;
  157. _RelDrive = relDrive;
  158. _FolderAndFiles = folderAndFiles;
  159. }
  160. [ContractInvariantMethod]
  161. private void Invariant()
  162. {
  163. Contract.Invariant(_Root != null);
  164. Contract.Invariant(_UNCPrefix != null);
  165. Contract.Invariant(_UNCLiteral != null);
  166. Contract.Invariant(_Options != null);
  167. Contract.Invariant(_Drive != null);
  168. Contract.Invariant(_DriveLetter != null);
  169. Contract.Invariant(_Server != null);
  170. Contract.Invariant(_IPv4 != null);
  171. Contract.Invariant(_IPv6 != null);
  172. Contract.Invariant(_ServerName != null);
  173. Contract.Invariant(_Device != null);
  174. Contract.Invariant(_DevicePrefix != null);
  175. Contract.Invariant(_DeviceName != null);
  176. Contract.Invariant(_DeviceGuid != null);
  177. Contract.Invariant(_NonRootPath != null);
  178. Contract.Invariant(_RelDrive != null);
  179. Contract.Invariant(_FolderAndFiles != null);
  180. }
  181. #endregion
  182. /// <summary>
  183. /// Examples of return values:
  184. /// <list>
  185. /// <item>\\?\UNC\C:\</item>
  186. /// <item>\\?\UNC\servername\</item>
  187. /// <item>\\192.168.0.2\</item>
  188. /// <item>C:\</item>
  189. /// </list>
  190. ///
  191. /// Definition: Returns part of the string that is in itself uniquely from the currently
  192. /// executing CLR.
  193. /// </summary>
  194. [Pure]
  195. public string Root
  196. {
  197. get { return _Root; }
  198. }
  199. /// <summary>
  200. /// Examples of return values:
  201. /// <list>
  202. /// <item></item>
  203. /// </list>
  204. /// </summary>
  205. [Pure]
  206. public string UNCPrefix
  207. {
  208. get { return _UNCPrefix; }
  209. }
  210. /// <summary>
  211. /// </summary>
  212. [Pure]
  213. public string UNCLiteral
  214. {
  215. get { return _UNCLiteral; }
  216. }
  217. /// <summary>
  218. /// </summary>
  219. [Pure]
  220. public string Options
  221. {
  222. get { return _Options; }
  223. }
  224. /// <summary>
  225. /// </summary>
  226. [Pure]
  227. public string Drive
  228. {
  229. get { return _Drive; }
  230. }
  231. /// <summary>
  232. /// </summary>
  233. [Pure]
  234. public string DriveLetter
  235. {
  236. get { return _DriveLetter; }
  237. }
  238. /// <summary>
  239. /// </summary>
  240. [Pure]
  241. public string Server
  242. {
  243. get { return _Server; }
  244. }
  245. /// <summary>
  246. /// Gets the 0.0.0.0-based IP-address if any.
  247. /// </summary>
  248. [Pure]
  249. public string IPv4
  250. {
  251. get { return _IPv4; }
  252. }
  253. /// <summary>
  254. /// </summary>
  255. [Pure]
  256. public string IPv6
  257. {
  258. get { return _IPv6; }
  259. }
  260. /// <summary>
  261. /// </summary>
  262. [Pure]
  263. public string ServerName
  264. {
  265. get { return _ServerName; }
  266. }
  267. /// <summary>
  268. /// </summary>
  269. [Pure]
  270. public string Device
  271. {
  272. get { return _Device; }
  273. }
  274. /// <summary>
  275. /// </summary>
  276. [Pure]
  277. public string DevicePrefix
  278. {
  279. get { return _DevicePrefix; }
  280. }
  281. /// <summary>
  282. /// </summary>
  283. [Pure]
  284. public string DeviceName
  285. {
  286. get { return _DeviceName; }
  287. }
  288. /// <summary>
  289. /// Gets the device GUID in the form
  290. /// <code>{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</code>
  291. /// i.e. 8-4-4-4-12 hex digits with curly brackets.
  292. /// </summary>
  293. [Pure]
  294. public string DeviceGuid
  295. {
  296. get { return _DeviceGuid; }
  297. }
  298. /// <summary>
  299. /// Gets a the part of the path that starts when the root ends.
  300. /// The root in turn is any UNC-prefix plus device, drive, server or ip-prefix.
  301. /// This string may not start with neither of '\' or '/'.
  302. /// </summary>
  303. [Pure]
  304. public string NonRootPath
  305. {
  306. get { return _NonRootPath; }
  307. }
  308. /// <summary>
  309. /// </summary>
  310. [Pure]
  311. public string RelDrive
  312. {
  313. get { return _RelDrive; }
  314. }
  315. /// <summary>
  316. /// The only time when this differs from <see cref = "NonRootPath" />
  317. /// is when a path like this is used:
  318. /// <code>C:../parent/a.txt</code>, otherwise, for all paths,
  319. /// this property equals <see cref = "NonRootPath" />.
  320. /// </summary>
  321. [Pure]
  322. public string FolderAndFiles
  323. {
  324. get
  325. {
  326. Contract.Ensures(Contract.Result<string>() != null);
  327. return _FolderAndFiles;
  328. }
  329. }
  330. [Pure]
  331. public PathType Type
  332. {
  333. get
  334. {
  335. if (!string.IsNullOrEmpty(Device))
  336. return PathType.Device;
  337. if (!string.IsNullOrEmpty(ServerName))
  338. return PathType.Server;
  339. if (!string.IsNullOrEmpty(IPv4))
  340. return PathType.IPv4;
  341. if (!string.IsNullOrEmpty(IPv6))
  342. return PathType.IPv6;
  343. if (!string.IsNullOrEmpty(Drive))
  344. return PathType.Drive;
  345. return PathType.Relative;
  346. }
  347. }
  348. /// <summary>
  349. /// Returns whether <see cref = "Root" /> is not an empty string.
  350. /// </summary>
  351. [Pure]
  352. public bool IsRooted
  353. {
  354. get { return !string.IsNullOrEmpty(_Root); }
  355. }
  356. /// <summary>
  357. /// Returns whether the current PathInfo is a valid parent of the child path info
  358. /// passed as argument.
  359. /// </summary>
  360. /// <param name = "child">The path info to verify</param>
  361. /// <returns>Whether it is true that the current path info is a parent of child.</returns>
  362. /// <exception cref = "NotSupportedException">If this instance of path info and child aren't rooted.</exception>
  363. [SuppressMessage("Microsoft.Performance", "CA1820:TestForEmptyStringsUsingStringLength",
  364. Justification = "Would change semantics")]
  365. public bool IsParentOf(PathInfo child)
  366. {
  367. if (Root == string.Empty || child.Root == string.Empty)
  368. throw new NotSupportedException("Non-rooted paths are not supported.");
  369. var OK = child.FolderAndFiles.StartsWith(FolderAndFiles);
  370. switch (Type)
  371. {
  372. case PathType.Device:
  373. OK &= child.DeviceName.ToLowerInvariant() == DeviceName.ToLowerInvariant();
  374. break;
  375. case PathType.Server:
  376. OK &= child.ServerName.ToLowerInvariant() == ServerName.ToLowerInvariant();
  377. break;
  378. case PathType.IPv4:
  379. OK &= IPAddress.Parse(child.IPv4).Equals(IPAddress.Parse(IPv4));
  380. break;
  381. case PathType.IPv6:
  382. OK &= (IPAddress.Parse(child.IPv6).Equals(IPAddress.Parse(IPv6)));
  383. break;
  384. case PathType.Relative:
  385. throw new InvalidOperationException("Since root isn't empty we should never get relative paths.");
  386. case PathType.Drive:
  387. OK &= DriveLetter.ToLowerInvariant() == child.DriveLetter.ToLowerInvariant();
  388. break;
  389. }
  390. return OK;
  391. }
  392. /// <summary>
  393. /// Removes the path info passes as a parameter from the current root. Only works for two rooted paths with same root.
  394. /// Does NOT cover all edge cases, please verify its intended results yourself.
  395. /// <example>
  396. /// </example>
  397. /// </summary>
  398. /// <param name = "other"></param>
  399. /// <returns></returns>
  400. public string RemoveParameterFromRoot(PathInfo other)
  401. {
  402. Contract.Requires(Root == other.Root, "roots must match to be able to subtract");
  403. Contract.Requires(FolderAndFiles.Length >= other.FolderAndFiles.Length,
  404. "The folders and files part of the parameter must be shorter or equal to in length, than that path you wish to subtract from.");
  405. if (other.FolderAndFiles == FolderAndFiles)
  406. return string.Empty;
  407. var startIndex = other.FolderAndFiles.Length;
  408. Contract.Assume(startIndex <= FolderAndFiles.Length);
  409. var substring = FolderAndFiles.Substring(startIndex);
  410. return substring.TrimStart(Path.GetDirectorySeparatorChars());
  411. }
  412. }
  413. }