PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Castle.IO/PathInfo.cs

https://github.com/castleproject/Castle.Transactions
C# | 591 lines | 438 code | 53 blank | 100 comment | 91 complexity | b0fbb5215427f2cabe92a1e15fb0a276 MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright 2004-2012 Castle Project, Henrik Feldt &contributors - https://github.com/castleproject
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. namespace Castle.IO
  15. {
  16. using System;
  17. using System.Diagnostics;
  18. using System.Diagnostics.Contracts;
  19. using System.Net;
  20. using System.Text.RegularExpressions;
  21. /// <summary>
  22. /// Immutable path data holder and value object that overrides Equals,
  23. /// implements IEquatable and overrides the == and != operators.
  24. ///
  25. /// Invariant: no fields nor properties are null after c'tor.
  26. /// </summary>
  27. [DebuggerDisplay(@"PathInfo: \{ Root: {Root}, Rest: {NonRootPath} \}")]
  28. public sealed class PathInfo : IEquatable<PathInfo>
  29. {
  30. internal const string UNCPrefixRegex = @"(?<UNC_prefix> \\\\\? (?<UNC_literal>\\UNC)? )";
  31. internal const string DeviceRegex =
  32. UNCPrefixRegex +
  33. @"?
  34. (?(UNC_prefix)|\\)
  35. (?<device>
  36. (?<dev_prefix>\\\.\\)
  37. (
  38. (?<dev_name>[\w\-]+)
  39. |(?<dev_guid>\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\})
  40. )
  41. )\\?";
  42. private const string DriveRegex =
  43. UNCPrefixRegex +
  44. @"?
  45. (?(UNC_prefix)\\)? # if we have an UNC prefix, there must be an extra backslash
  46. (?<drive>
  47. (?<drive_letter>[A-Z]{1,3})
  48. : # the :-character after the drive letter
  49. (\\|/) # the trailing slash
  50. )";
  51. internal const string ServerRegex =
  52. UNCPrefixRegex +
  53. @"?
  54. (?(UNC_prefix)|\\) #this is optional IIF we have the UNC_prefix, so only match \\ if we did not have it
  55. (?<server>
  56. (?<server_prefix>\\)
  57. (?:
  58. (?<ipv4>(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})
  59. |(?:\[(?<ipv6>[A-F0-9:]{3,39})\])
  60. |(?<server_name>(?!UNC)[\w\-]+) # allow dashes in server names, but ignore servers named UNC
  61. )
  62. )\\?";
  63. private const string StrRegex =
  64. @"
  65. (?<root>
  66. (" + DriveRegex + @")
  67. |(" + ServerRegex + @")
  68. |(" + DeviceRegex +
  69. @")
  70. |/
  71. |\\
  72. )?
  73. (?<nonrootpath>
  74. (?!\\)
  75. (?<rel_drive>\w{1,3}:)?
  76. (?<folders_files>.+))?";
  77. private static readonly Regex regex = new Regex(StrRegex,
  78. RegexOptions.Compiled |
  79. RegexOptions.IgnorePatternWhitespace |
  80. RegexOptions.IgnoreCase |
  81. RegexOptions.Multiline);
  82. private readonly string root,
  83. uncPrefix,
  84. uncLiteral,
  85. drive,
  86. driveLetter,
  87. server,
  88. iPv4,
  89. iPv6,
  90. serverName,
  91. device,
  92. devicePrefix,
  93. deviceName,
  94. deviceGuid,
  95. nonRootPath,
  96. relDrive,
  97. folderAndFiles;
  98. // too stupid checker
  99. [ContractVerification(false)]
  100. public static PathInfo Parse(string path)
  101. {
  102. Contract.Requires(path != null);
  103. Contract.Ensures(Contract.Result<PathInfo>() != null);
  104. var matches = regex.Matches(path);
  105. Func<string, string> m = s => GetMatch(matches, s);
  106. // this might be possible to improve using raw indicies (ints) instead.
  107. return new PathInfo(
  108. m("root"),
  109. m("UNC_prefix"),
  110. m("UNC_literal"),
  111. m("drive"),
  112. m("drive_letter"),
  113. m("server"),
  114. m("ipv4"),
  115. m("ipv6"),
  116. m("server_name"),
  117. m("device"),
  118. m("dev_prefix"),
  119. m("dev_name"),
  120. m("dev_guid"),
  121. m("nonrootpath"),
  122. m("rel_drive"),
  123. m("folders_files")
  124. );
  125. }
  126. internal static string GetMatch(MatchCollection matches,
  127. string groupIndex)
  128. {
  129. Contract.Requires(matches != null);
  130. Contract.Ensures(Contract.Result<string>() != null);
  131. var matchC = matches.Count;
  132. for (var i = 0; i < matchC; i++)
  133. if (matches[i].Groups[groupIndex].Success)
  134. return matches[i].Groups[groupIndex].Value;
  135. return string.Empty;
  136. }
  137. #region c'tor and non null invariants
  138. private PathInfo(string root, string uncPrefix, string uncLiteral, string drive, string driveLetter,
  139. string server, string iPv4, string iPv6, string serverName, string device, string devicePrefix,
  140. string deviceName, string deviceGuid, string nonRootPath, string relDrive, string folderAndFiles)
  141. {
  142. Contract.Requires(root != null);
  143. Contract.Requires(uncPrefix != null);
  144. Contract.Requires(uncLiteral != null);
  145. Contract.Requires(drive != null);
  146. Contract.Requires(driveLetter != null);
  147. Contract.Requires(server != null);
  148. Contract.Requires(iPv4 != null);
  149. Contract.Requires(iPv6 != null);
  150. Contract.Requires(serverName != null);
  151. Contract.Requires(device != null);
  152. Contract.Requires(devicePrefix != null);
  153. Contract.Requires(deviceName != null);
  154. Contract.Requires(deviceGuid != null);
  155. Contract.Requires(nonRootPath != null);
  156. Contract.Requires(relDrive != null);
  157. Contract.Requires(folderAndFiles != null);
  158. this.root = root;
  159. this.uncPrefix = uncPrefix;
  160. this.uncLiteral = uncLiteral;
  161. this.drive = drive;
  162. this.driveLetter = driveLetter;
  163. this.server = server;
  164. this.iPv4 = iPv4;
  165. this.iPv6 = iPv6;
  166. this.serverName = serverName;
  167. this.device = device;
  168. this.devicePrefix = devicePrefix;
  169. this.deviceName = deviceName;
  170. this.deviceGuid = deviceGuid;
  171. this.nonRootPath = nonRootPath;
  172. this.relDrive = relDrive;
  173. this.folderAndFiles = folderAndFiles;
  174. }
  175. [ContractInvariantMethod]
  176. private void Invariant()
  177. {
  178. Contract.Invariant(root != null);
  179. Contract.Invariant(uncPrefix != null);
  180. Contract.Invariant(uncLiteral != null);
  181. Contract.Invariant(drive != null);
  182. Contract.Invariant(driveLetter != null);
  183. Contract.Invariant(server != null);
  184. Contract.Invariant(iPv4 != null);
  185. Contract.Invariant(iPv6 != null);
  186. Contract.Invariant(serverName != null);
  187. Contract.Invariant(device != null);
  188. Contract.Invariant(devicePrefix != null);
  189. Contract.Invariant(deviceName != null);
  190. Contract.Invariant(deviceGuid != null);
  191. Contract.Invariant(nonRootPath != null);
  192. Contract.Invariant(relDrive != null);
  193. Contract.Invariant(folderAndFiles != null);
  194. }
  195. #endregion
  196. /// <summary>
  197. /// Examples of return values:
  198. /// <list>
  199. /// <item>\\?\UNC\C:\</item>
  200. /// <item>\\?\UNC\servername\</item>
  201. /// <item>\\192.168.0.2\</item>
  202. /// <item>C:\</item>
  203. /// </list>
  204. ///
  205. /// Definition: Returns part of the string that is in itself uniquely from the currently
  206. /// executing CLR.
  207. /// </summary>
  208. [Pure]
  209. public string Root
  210. {
  211. get
  212. {
  213. Contract.Ensures(Contract.Result<string>() != null);
  214. return root;
  215. }
  216. }
  217. /// <summary>
  218. /// Examples of return values:
  219. /// <list>
  220. /// <item></item>
  221. /// </list>
  222. /// </summary>
  223. [Pure]
  224. public string UNCPrefix
  225. {
  226. get
  227. {
  228. Contract.Ensures(Contract.Result<string>() != null);
  229. return uncPrefix;
  230. }
  231. }
  232. /// <summary>
  233. /// </summary>
  234. [Pure]
  235. public string UNCLiteral
  236. {
  237. get
  238. {
  239. Contract.Ensures(Contract.Result<string>() != null);
  240. return uncLiteral;
  241. }
  242. }
  243. /// <summary>
  244. /// </summary>
  245. [Pure]
  246. public string Drive
  247. {
  248. get
  249. {
  250. Contract.Ensures(Contract.Result<string>() != null);
  251. return drive;
  252. }
  253. }
  254. /// <summary>
  255. /// </summary>
  256. [Pure]
  257. public string DriveLetter
  258. {
  259. get
  260. {
  261. Contract.Ensures(Contract.Result<string>() != null);
  262. return driveLetter;
  263. }
  264. }
  265. /// <summary>
  266. /// </summary>
  267. [Pure]
  268. public string Server
  269. {
  270. get
  271. {
  272. Contract.Ensures(Contract.Result<string>() != null);
  273. return server;
  274. }
  275. }
  276. /// <summary>
  277. /// Gets the IPv4 IP-address if any. <see cref = "IPAddress.None" />
  278. /// if none was found.
  279. /// </summary>
  280. [Pure]
  281. public IPAddress IPv4
  282. {
  283. get
  284. {
  285. Contract.Ensures(Contract.Result<IPAddress>() != null);
  286. IPAddress addr;
  287. return !string.IsNullOrEmpty(iPv4) && IPAddress.TryParse(iPv4, out addr)
  288. ? addr
  289. : IPAddress.None;
  290. }
  291. }
  292. /// <summary>
  293. /// Gets the IPv6 IP-address if any. <see cref = "IPAddress.None" />
  294. /// if non was found.
  295. /// </summary>
  296. [Pure]
  297. public IPAddress IPv6
  298. {
  299. get
  300. {
  301. Contract.Ensures(Contract.Result<IPAddress>() != null);
  302. IPAddress addr;
  303. return !string.IsNullOrEmpty(iPv6) && IPAddress.TryParse(iPv6, out addr)
  304. ? addr
  305. : IPAddress.IPv6None;
  306. }
  307. }
  308. /// <summary>
  309. /// </summary>
  310. [Pure]
  311. public string ServerName
  312. {
  313. get
  314. {
  315. Contract.Ensures(Contract.Result<string>() != null);
  316. return serverName;
  317. }
  318. }
  319. /// <summary>
  320. /// </summary>
  321. [Pure]
  322. public string Device
  323. {
  324. get
  325. {
  326. Contract.Ensures(Contract.Result<string>() != null);
  327. return device;
  328. }
  329. }
  330. /// <summary>
  331. /// </summary>
  332. [Pure]
  333. public string DevicePrefix
  334. {
  335. get
  336. {
  337. Contract.Ensures(Contract.Result<string>() != null);
  338. return devicePrefix;
  339. }
  340. }
  341. /// <summary>
  342. /// </summary>
  343. [Pure]
  344. public string DeviceName
  345. {
  346. get
  347. {
  348. Contract.Ensures(Contract.Result<string>() != null);
  349. return deviceName;
  350. }
  351. }
  352. /// <summary>
  353. /// Gets the device GUID in the form
  354. /// <code>{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</code>
  355. /// i.e. 8-4-4-4-12 hex digits with curly brackets.
  356. /// </summary>
  357. [Pure]
  358. public Guid DeviceGuid
  359. {
  360. get { return deviceGuid == string.Empty ? Guid.Empty : Guid.Parse(deviceGuid); }
  361. }
  362. /// <summary>
  363. /// Gets a the part of the path that starts when the root ends.
  364. /// The root in turn is any UNC-prefix plus device, drive, server or ip-prefix.
  365. /// This string may not start with neither of '\' or '/'.
  366. /// </summary>
  367. [Pure]
  368. public string NonRootPath
  369. {
  370. get
  371. {
  372. Contract.Ensures(Contract.Result<string>() != null);
  373. return nonRootPath;
  374. }
  375. }
  376. /// <summary>
  377. /// </summary>
  378. [Pure]
  379. public string RelDrive
  380. {
  381. get
  382. {
  383. Contract.Ensures(Contract.Result<string>() != null);
  384. return relDrive;
  385. }
  386. }
  387. /// <summary>
  388. /// The only time when this differs from <see cref = "NonRootPath" />
  389. /// is when a path like this is used:
  390. /// <code>C:../parent/a.txt</code>, otherwise, for all paths,
  391. /// this property equals <see cref = "NonRootPath" />.
  392. /// </summary>
  393. [Pure]
  394. public string FolderAndFiles
  395. {
  396. get
  397. {
  398. Contract.Ensures(Contract.Result<string>() != null);
  399. return folderAndFiles;
  400. }
  401. }
  402. [Pure]
  403. public PathType Type
  404. {
  405. get
  406. {
  407. if (!string.IsNullOrEmpty(Device))
  408. return PathType.Device;
  409. if (!string.IsNullOrEmpty(ServerName))
  410. return PathType.Server;
  411. if (IPv4 != IPAddress.None)
  412. return PathType.IPv4;
  413. if (IPv6 != IPAddress.IPv6None)
  414. return PathType.IPv6;
  415. if (!string.IsNullOrEmpty(Drive))
  416. return PathType.Drive;
  417. return PathType.Relative;
  418. }
  419. }
  420. /// <summary>
  421. /// Returns whether <see cref = "Root" /> is not an empty string.
  422. /// </summary>
  423. [Pure]
  424. public bool IsRooted
  425. {
  426. get { return !string.IsNullOrEmpty(root); }
  427. }
  428. /// <summary>
  429. /// Returns whether the current PathInfo is a valid parent of the child path info
  430. /// passed as argument.
  431. /// </summary>
  432. /// <param name = "child">The path info to verify</param>
  433. /// <returns>Whether it is true that the current path info is a parent of child.</returns>
  434. /// <exception cref = "NotSupportedException">If this instance of path info and child aren't rooted.</exception>
  435. public bool IsParentOf(PathInfo child)
  436. {
  437. Contract.Requires(child != null);
  438. if (Root == string.Empty || child.Root == string.Empty)
  439. throw new NotSupportedException("Non-rooted paths are not supported.");
  440. // TODO: Normalize Path
  441. var OK = child.FolderAndFiles.StartsWith(FolderAndFiles);
  442. switch (Type)
  443. {
  444. case PathType.Device:
  445. OK &= child.DeviceName.Equals(DeviceName, StringComparison.InvariantCultureIgnoreCase);
  446. break;
  447. case PathType.Server:
  448. OK &= child.ServerName.Equals(ServerName, StringComparison.InvariantCultureIgnoreCase);
  449. break;
  450. case PathType.IPv4:
  451. OK &= child.IPv4.Equals(IPv4);
  452. break;
  453. case PathType.IPv6:
  454. OK &= child.IPv6.Equals(IPv6);
  455. break;
  456. case PathType.Relative:
  457. throw new NotSupportedException("Since root isn't empty we should never get relative paths.");
  458. case PathType.Drive:
  459. OK &= DriveLetter.ToLowerInvariant() == child.DriveLetter.ToLowerInvariant();
  460. break;
  461. }
  462. return OK;
  463. }
  464. /// <summary>
  465. /// Removes the path info passes as a parameter from the current root. Only works for two rooted paths with same root.
  466. /// Does NOT cover all edge cases, please verify its intended results yourself.
  467. /// <example>
  468. /// </example>
  469. /// </summary>
  470. /// <param name = "other"></param>
  471. /// <returns></returns>
  472. public string RemoveParameterFromRoot(PathInfo other)
  473. {
  474. Contract.Requires(Root == other.Root, "roots must match to be able to subtract");
  475. Contract.Requires(FolderAndFiles.Length >= other.FolderAndFiles.Length,
  476. "The folders and files part of the parameter must be shorter or equal to in length, than that path you wish to subtract from.");
  477. if (other.FolderAndFiles == FolderAndFiles)
  478. return string.Empty;
  479. var startIndex = other.FolderAndFiles.Length;
  480. Contract.Assume(startIndex <= FolderAndFiles.Length);
  481. var substring = FolderAndFiles.Substring(startIndex);
  482. return substring.TrimStart(Path.GetDirectorySeparatorChars());
  483. }
  484. public bool Equals(PathInfo other)
  485. {
  486. if (ReferenceEquals(null, other)) return false;
  487. if (ReferenceEquals(this, other)) return true;
  488. return Equals(other.drive, drive)
  489. && Equals(other.driveLetter, driveLetter)
  490. && Equals(other.server, server)
  491. && Equals(other.iPv4, iPv4)
  492. && Equals(other.iPv6, iPv6)
  493. && Equals(other.serverName, serverName)
  494. && Equals(other.device, device)
  495. && Equals(other.devicePrefix, devicePrefix)
  496. && Equals(other.deviceName, deviceName)
  497. && Equals(other.deviceGuid, deviceGuid)
  498. && Equals(other.nonRootPath, nonRootPath)
  499. && Equals(other.relDrive, relDrive)
  500. && Equals(other.folderAndFiles, folderAndFiles);
  501. }
  502. public override bool Equals(object obj)
  503. {
  504. if (ReferenceEquals(null, obj)) return false;
  505. if (ReferenceEquals(this, obj)) return true;
  506. if (obj.GetType() != typeof(PathInfo)) return false;
  507. return Equals((PathInfo)obj);
  508. }
  509. public override int GetHashCode()
  510. {
  511. unchecked
  512. {
  513. var result = drive.GetHashCode();
  514. result = (result*397) ^ driveLetter.GetHashCode();
  515. result = (result*397) ^ server.GetHashCode();
  516. result = (result*397) ^ iPv4.GetHashCode();
  517. result = (result*397) ^ iPv6.GetHashCode();
  518. result = (result*397) ^ serverName.GetHashCode();
  519. result = (result*397) ^ device.GetHashCode();
  520. result = (result*397) ^ devicePrefix.GetHashCode();
  521. result = (result*397) ^ deviceName.GetHashCode();
  522. result = (result*397) ^ deviceGuid.GetHashCode();
  523. result = (result*397) ^ nonRootPath.GetHashCode();
  524. result = (result*397) ^ relDrive.GetHashCode();
  525. result = (result*397) ^ folderAndFiles.GetHashCode();
  526. return result;
  527. }
  528. }
  529. public static bool operator ==(PathInfo left, PathInfo right)
  530. {
  531. return Equals(left, right);
  532. }
  533. public static bool operator !=(PathInfo left, PathInfo right)
  534. {
  535. return !Equals(left, right);
  536. }
  537. }
  538. }