1
0

FilePathSyntax.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // MARK: - Query API
  8. @available(System 0.0.2, *)
  9. extension FilePath {
  10. /// Returns true if this path uniquely identifies the location of
  11. /// a file without reference to an additional starting location.
  12. ///
  13. /// On Unix platforms, absolute paths begin with a `/`. `isAbsolute` is
  14. /// equivalent to `root != nil`.
  15. ///
  16. /// On Windows, absolute paths are fully qualified paths. `isAbsolute` is
  17. /// _not_ equivalent to `root != nil` for traditional DOS paths
  18. /// (e.g. `C:foo` and `\bar` have roots but are not absolute). UNC paths
  19. /// and device paths are always absolute. Traditional DOS paths are
  20. /// absolute only if they begin with a volume or drive followed by
  21. /// a `:` and a separator.
  22. ///
  23. /// NOTE: This does not perform shell expansion or substitute
  24. /// environment variables; paths beginning with `~` are considered relative.
  25. ///
  26. /// Examples:
  27. /// * Unix:
  28. /// * `/usr/local/bin`
  29. /// * `/tmp/foo.txt`
  30. /// * `/`
  31. /// * Windows:
  32. /// * `C:\Users\`
  33. /// * `\\?\UNC\server\share\bar.exe`
  34. /// * `\\server\share\bar.exe`
  35. public var isAbsolute: Bool {
  36. self.root?.isAbsolute ?? false
  37. }
  38. /// Returns true if this path is not absolute (see `isAbsolute`).
  39. ///
  40. /// Examples:
  41. /// * Unix:
  42. /// * `~/bar`
  43. /// * `tmp/foo.txt`
  44. /// * Windows:
  45. /// * `bar\baz`
  46. /// * `C:Users\`
  47. /// * `\Users`
  48. public var isRelative: Bool { !isAbsolute }
  49. // TODO(Windows docs): examples with roots, such as whether `\foo\bar`
  50. // starts with `C:\foo`
  51. /// Returns whether `other` is a prefix of `self`, only considering
  52. /// whole path components.
  53. ///
  54. /// Example:
  55. ///
  56. /// let path: FilePath = "/usr/bin/ls"
  57. /// path.starts(with: "/") // true
  58. /// path.starts(with: "/usr/bin") // true
  59. /// path.starts(with: "/usr/bin/ls") // true
  60. /// path.starts(with: "/usr/bin/ls///") // true
  61. /// path.starts(with: "/us") // false
  62. public func starts(with other: FilePath) -> Bool {
  63. guard !other.isEmpty else { return true }
  64. return self.root == other.root && components.starts(
  65. with: other.components)
  66. }
  67. // TODO(Windows docs): examples with roots, such as whether `C:\foo\bar`
  68. // ends with `C:bar`
  69. /// Returns whether `other` is a suffix of `self`, only considering
  70. /// whole path components.
  71. ///
  72. /// Example:
  73. ///
  74. /// let path: FilePath = "/usr/bin/ls"
  75. /// path.ends(with: "ls") // true
  76. /// path.ends(with: "bin/ls") // true
  77. /// path.ends(with: "usr/bin/ls") // true
  78. /// path.ends(with: "/usr/bin/ls///") // true
  79. /// path.ends(with: "/ls") // false
  80. public func ends(with other: FilePath) -> Bool {
  81. if other.root != nil {
  82. // TODO: anything tricky here for Windows?
  83. return self == other
  84. }
  85. return components.reversed().starts(
  86. with: other.components.reversed())
  87. }
  88. /// Whether this path is empty
  89. public var isEmpty: Bool { _storage.isEmpty }
  90. }
  91. // MARK: - Decompose a path
  92. @available(System 0.0.2, *)
  93. extension FilePath {
  94. /// Returns the root of a path if there is one, otherwise `nil`.
  95. ///
  96. /// On Unix, this will return the leading `/` if the path is absolute
  97. /// and `nil` if the path is relative.
  98. ///
  99. /// On Windows, for traditional DOS paths, this will return
  100. /// the path prefix up to and including a root directory or
  101. /// a supplied drive or volume. Otherwise, if the path is relative to
  102. /// both the current directory and current drive, returns `nil`.
  103. ///
  104. /// On Windows, for UNC or device paths, this will return the path prefix
  105. /// up to and including the host and share for UNC paths or the volume for
  106. /// device paths followed by any subsequent separator.
  107. ///
  108. /// Examples:
  109. /// * Unix:
  110. /// * `/foo/bar => /`
  111. /// * `foo/bar => nil`
  112. /// * Windows:
  113. /// * `C:\foo\bar => C:\`
  114. /// * `C:foo\bar => C:`
  115. /// * `\foo\bar => \ `
  116. /// * `foo\bar => nil`
  117. /// * `\\server\share\file => \\server\share\`
  118. /// * `\\?\UNC\server\share\file => \\?\UNC\server\share\`
  119. /// * `\\.\device\folder => \\.\device\`
  120. ///
  121. /// Setting the root to `nil` will remove the root and setting a new
  122. /// root will replace the root.
  123. ///
  124. /// Example:
  125. ///
  126. /// var path: FilePath = "/foo/bar"
  127. /// path.root = nil // path is "foo/bar"
  128. /// path.root = "/" // path is "/foo/bar"
  129. ///
  130. /// Example (Windows):
  131. ///
  132. /// var path: FilePath = #"\foo\bar"#
  133. /// path.root = nil // path is #"foo\bar"#
  134. /// path.root = "C:" // path is #"C:foo\bar"#
  135. /// path.root = #"C:\"# // path is #"C:\foo\bar"#
  136. public var root: FilePath.Root? {
  137. get {
  138. guard _hasRoot else { return nil }
  139. return Root(self, rootEnd: _relativeStart)
  140. }
  141. set {
  142. defer { _invariantCheck() }
  143. guard let r = newValue else {
  144. _storage.removeSubrange(..<_relativeStart)
  145. return
  146. }
  147. _storage.replaceSubrange(..<_relativeStart, with: r._slice)
  148. }
  149. }
  150. /// Creates a new path containing just the components, i.e. everything
  151. /// after `root`.
  152. ///
  153. /// Returns self if `root == nil`.
  154. ///
  155. /// Examples:
  156. /// * Unix:
  157. /// * `/foo/bar => foo/bar`
  158. /// * `foo/bar => foo/bar`
  159. /// * `/ => ""`
  160. /// * Windows:
  161. /// * `C:\foo\bar => foo\bar`
  162. /// * `foo\bar => foo\bar`
  163. /// * `\\?\UNC\server\share\file => file`
  164. /// * `\\?\device\folder\file.exe => folder\file.exe`
  165. /// * `\\server\share\file => file`
  166. /// * `\ => ""`
  167. public __consuming func removingRoot() -> FilePath {
  168. var copy = self
  169. copy.root = nil
  170. return copy
  171. }
  172. }
  173. @available(System 0.0.2, *)
  174. extension FilePath {
  175. /// Returns the final component of the path.
  176. /// Returns `nil` if the path is empty or only contains a root.
  177. ///
  178. /// Note: Even if the final component is a special directory
  179. /// (`.` or `..`), it will still be returned. See `lexicallyNormalize()`.
  180. ///
  181. /// Examples:
  182. /// * Unix:
  183. /// * `/usr/local/bin/ => bin`
  184. /// * `/tmp/foo.txt => foo.txt`
  185. /// * `/tmp/foo.txt/.. => ..`
  186. /// * `/tmp/foo.txt/. => .`
  187. /// * `/ => nil`
  188. /// * Windows:
  189. /// * `C:\Users\ => Users`
  190. /// * `C:Users\ => Users`
  191. /// * `C:\ => nil`
  192. /// * `\Users\ => Users`
  193. /// * `\\?\UNC\server\share\bar.exe => bar.exe`
  194. /// * `\\server\share => nil`
  195. /// * `\\?\UNC\server\share\ => nil`
  196. public var lastComponent: Component? { components.last }
  197. /// Creates a new path with everything up to but not including
  198. /// `lastComponent`.
  199. ///
  200. /// If the path only contains a root, returns `self`.
  201. /// If the path has no root and only includes a single component,
  202. /// returns an empty FilePath.
  203. ///
  204. /// Examples:
  205. /// * Unix:
  206. /// * `/usr/bin/ls => /usr/bin`
  207. /// * `/foo => /`
  208. /// * `/ => /`
  209. /// * `foo => ""`
  210. /// * Windows:
  211. /// * `C:\foo\bar.exe => C:\foo`
  212. /// * `C:\ => C:\`
  213. /// * `\\server\share\folder\file.txt => \\server\share\folder`
  214. /// * `\\server\share\ => \\server\share\`
  215. public __consuming func removingLastComponent() -> FilePath {
  216. var copy = self
  217. copy.removeLastComponent()
  218. return copy
  219. }
  220. /// In-place mutating variant of `removingLastComponent`.
  221. ///
  222. /// If `self` only contains a root, does nothing and returns `false`.
  223. /// Otherwise removes `lastComponent` and returns `true`.
  224. ///
  225. /// Example:
  226. ///
  227. /// var path = "/usr/bin"
  228. /// path.removeLastComponent() == true // path is "/usr"
  229. /// path.removeLastComponent() == true // path is "/"
  230. /// path.removeLastComponent() == false // path is "/"
  231. @discardableResult
  232. public mutating func removeLastComponent() -> Bool {
  233. defer { _invariantCheck() }
  234. guard let lastRel = lastComponent else { return false }
  235. _storage.removeSubrange(lastRel._slice.indices)
  236. _removeTrailingSeparator()
  237. return true
  238. }
  239. }
  240. @available(System 0.0.2, *)
  241. extension FilePath.Component {
  242. /// The extension of this file or directory component.
  243. ///
  244. /// If `self` does not contain a `.` anywhere, or only
  245. /// at the start, returns `nil`. Otherwise, returns everything after the dot.
  246. ///
  247. /// Examples:
  248. /// * `foo.txt => txt`
  249. /// * `foo.tar.gz => gz`
  250. /// * `Foo.app => app`
  251. /// * `.hidden => nil`
  252. /// * `.. => nil`
  253. public var `extension`: String? {
  254. guard let range = _extensionRange() else { return nil }
  255. return _slice[range].string
  256. }
  257. /// The non-extension portion of this file or directory component.
  258. ///
  259. /// Examples:
  260. /// * `foo.txt => foo`
  261. /// * `foo.tar.gz => foo.tar`
  262. /// * `Foo.app => Foo`
  263. /// * `.hidden => .hidden`
  264. /// * `.. => ..`
  265. public var stem: String {
  266. _slice[_stemRange()].string
  267. }
  268. }
  269. @available(System 0.0.2, *)
  270. extension FilePath {
  271. /// The extension of the file or directory last component.
  272. ///
  273. /// If `lastComponent` is `nil` or one of the special path components
  274. /// `.` or `..`, `get` returns `nil` and `set` does nothing.
  275. ///
  276. /// If `lastComponent` does not contain a `.` anywhere, or only
  277. /// at the start, `get` returns `nil` and `set` will append a
  278. /// `.` and `newValue` to `lastComponent`.
  279. ///
  280. /// Otherwise `get` returns everything after the last `.` and `set` will
  281. /// replace the extension.
  282. ///
  283. /// Examples:
  284. /// * `/tmp/foo.txt => txt`
  285. /// * `/Applications/Foo.app/ => app`
  286. /// * `/Applications/Foo.app/bar.txt => txt`
  287. /// * `/tmp/foo.tar.gz => gz`
  288. /// * `/tmp/.hidden => nil`
  289. /// * `/tmp/.hidden. => ""`
  290. /// * `/tmp/.. => nil`
  291. ///
  292. /// Example:
  293. ///
  294. /// var path = "/tmp/file"
  295. /// path.extension = "txt" // path is "/tmp/file.txt"
  296. /// path.extension = "o" // path is "/tmp/file.o"
  297. /// path.extension = nil // path is "/tmp/file"
  298. /// path.extension = "" // path is "/tmp/file."
  299. public var `extension`: String? {
  300. get { lastComponent?.extension }
  301. set {
  302. defer { _invariantCheck() }
  303. guard let base = lastComponent, base.kind == .regular else { return }
  304. let suffix: SystemString
  305. if let ext = newValue {
  306. suffix = _makeExtension(ext)
  307. } else {
  308. suffix = SystemString()
  309. }
  310. let extRange = (
  311. base._extensionIndex() ?? base._slice.endIndex
  312. ) ..< base._slice.endIndex
  313. _storage.replaceSubrange(extRange, with: suffix)
  314. }
  315. }
  316. /// The non-extension portion of the file or directory last component.
  317. ///
  318. /// Returns `nil` if `lastComponent` is `nil`
  319. ///
  320. /// * `/tmp/foo.txt => foo`
  321. /// * `/Applications/Foo.app/ => Foo`
  322. /// * `/Applications/Foo.app/bar.txt => bar`
  323. /// * `/tmp/.hidden => .hidden`
  324. /// * `/tmp/.. => ..`
  325. /// * `/ => nil`
  326. public var stem: String? { lastComponent?.stem }
  327. }
  328. @available(System 0.0.2, *)
  329. extension FilePath {
  330. /// Whether the path is in lexical-normal form, that is `.` and `..`
  331. /// components have been collapsed lexically (i.e. without following
  332. /// symlinks).
  333. ///
  334. /// Examples:
  335. /// * `"/usr/local/bin".isLexicallyNormal == true`
  336. /// * `"../local/bin".isLexicallyNormal == true`
  337. /// * `"local/bin/..".isLexicallyNormal == false`
  338. public var isLexicallyNormal: Bool {
  339. // `..` components are permitted at the front of a
  340. // relative path, otherwise there should be no special directories
  341. //
  342. // FIXME: Windows `C:..\foo\bar` should probably be lexically normal, but
  343. // `\..\foo\bar` should not.
  344. components.drop(
  345. while: { root == nil && $0.kind == .parentDirectory }
  346. ).allSatisfy { $0.kind == .regular }
  347. }
  348. /// Collapse `.` and `..` components lexically (i.e. without following
  349. /// symlinks).
  350. ///
  351. /// Examples:
  352. /// * `/usr/./local/bin/.. => /usr/local`
  353. /// * `/../usr/local/bin => /usr/local/bin`
  354. /// * `../usr/local/../bin => ../usr/bin`
  355. public mutating func lexicallyNormalize() {
  356. defer { _invariantCheck() }
  357. _normalizeSpecialDirectories()
  358. }
  359. /// Returns a copy of `self` in lexical-normal form, that is `.` and `..`
  360. /// components have been collapsed lexically (i.e. without following
  361. /// symlinks). See `lexicallyNormalize`
  362. public __consuming func lexicallyNormalized() -> FilePath {
  363. var copy = self
  364. copy.lexicallyNormalize()
  365. return copy
  366. }
  367. /// Create a new `FilePath` by resolving `subpath` relative to `self`,
  368. /// ensuring that the result is lexically contained within `self`.
  369. ///
  370. /// `subpath` will be lexically normalized (see `lexicallyNormalize`) as
  371. /// part of resolution, meaning any contained `.` and `..` components will
  372. /// be collapsed without resolving symlinks. Any root in `subpath` will be
  373. /// ignored.
  374. ///
  375. /// Returns `nil` if the result would "escape" from `self` through use of
  376. /// the special directory component `..`.
  377. ///
  378. /// This is useful for protecting against arbitrary path traversal from an
  379. /// untrusted subpath: the result is guaranteed to be lexically contained
  380. /// within `self`. Since this operation does not consult the file system to
  381. /// resolve symlinks, any escaping symlinks nested inside of `self` can still
  382. /// be targeted by the result.
  383. ///
  384. /// Example:
  385. ///
  386. /// let staticContent: FilePath = "/var/www/my-website/static"
  387. /// let links: [FilePath] =
  388. /// ["index.html", "/assets/main.css", "../../../../etc/passwd"]
  389. /// links.map { staticContent.lexicallyResolving($0) }
  390. /// // ["/var/www/my-website/static/index.html",
  391. /// // "/var/www/my-website/static/assets/main.css",
  392. /// // nil]
  393. public __consuming func lexicallyResolving(
  394. _ subpath: __owned FilePath
  395. ) -> FilePath? {
  396. let subpath = subpath.removingRoot().lexicallyNormalized()
  397. guard !subpath.isEmpty else { return self }
  398. guard subpath.components.first?.kind != .parentDirectory else {
  399. return nil
  400. }
  401. return self.appending(subpath.components)
  402. }
  403. }
  404. // Modification and concatenation API
  405. @available(System 0.0.2, *)
  406. extension FilePath {
  407. // TODO(Windows docs): example with roots
  408. /// If `prefix` is a prefix of `self`, removes it and returns `true`.
  409. /// Otherwise returns `false`.
  410. ///
  411. /// Example:
  412. ///
  413. /// var path: FilePath = "/usr/local/bin"
  414. /// path.removePrefix("/usr/bin") // false
  415. /// path.removePrefix("/us") // false
  416. /// path.removePrefix("/usr/local") // true, path is "bin"
  417. public mutating func removePrefix(_ prefix: FilePath) -> Bool {
  418. defer { _invariantCheck() }
  419. // FIXME: Should Windows have more nuanced semantics?
  420. guard root == prefix.root else { return false }
  421. let (tail, remainder) = _dropCommonPrefix(components, prefix.components)
  422. guard remainder.isEmpty else { return false }
  423. self._storage.removeSubrange(..<tail.startIndex._storage)
  424. return true
  425. }
  426. // TODO(Windows docs): example with roots
  427. /// Append a `component` on to the end of this path.
  428. ///
  429. /// Example:
  430. ///
  431. /// var path: FilePath = "/tmp"
  432. /// let sub: FilePath = "foo/./bar/../baz/."
  433. /// for comp in sub.components.filter({ $0.kind != .currentDirectory }) {
  434. /// path.append(comp)
  435. /// }
  436. /// // path is "/tmp/foo/bar/../baz"
  437. public mutating func append(_ component: __owned FilePath.Component) {
  438. defer { _invariantCheck() }
  439. _append(unchecked: component._slice)
  440. }
  441. // TODO(Windows docs): example with roots
  442. /// Append `components` on to the end of this path.
  443. ///
  444. /// Example:
  445. ///
  446. /// var path: FilePath = "/"
  447. /// path.append(["usr", "local"]) // path is "/usr/local"
  448. /// let otherPath: FilePath = "/bin/ls"
  449. /// path.append(otherPath.components) // path is "/usr/local/bin/ls"
  450. public mutating func append<C: Collection>(
  451. _ components: __owned C
  452. ) where C.Element == FilePath.Component {
  453. defer { _invariantCheck() }
  454. for c in components {
  455. _append(unchecked: c._slice)
  456. }
  457. }
  458. // TODO(Windows docs): example with roots, should we rephrase this "spurious
  459. // roots"?
  460. /// Append the contents of `other`, ignoring any spurious leading separators.
  461. ///
  462. /// A leading separator is spurious if `self` is non-empty.
  463. ///
  464. /// Example:
  465. ///
  466. /// var path: FilePath = ""
  467. /// path.append("/var/www/website") // "/var/www/website"
  468. /// path.append("static/assets") // "/var/www/website/static/assets"
  469. /// path.append("/main.css") // "/var/www/website/static/assets/main.css"
  470. public mutating func append(_ other: __owned String) {
  471. defer { _invariantCheck() }
  472. guard !other.utf8.isEmpty else { return }
  473. guard !isEmpty else {
  474. self = FilePath(other)
  475. return
  476. }
  477. let otherPath = FilePath(other)
  478. _append(unchecked: otherPath._storage[otherPath._relativeStart...])
  479. }
  480. // TODO(Windows docs): example with roots
  481. /// Non-mutating version of `append(_:Component)`.
  482. public __consuming func appending(_ other: __owned Component) -> FilePath {
  483. var copy = self
  484. copy.append(other)
  485. return copy
  486. }
  487. // TODO(Windows docs): example with roots
  488. /// Non-mutating version of `append(_:C)`.
  489. public __consuming func appending<C: Collection>(
  490. _ components: __owned C
  491. ) -> FilePath where C.Element == FilePath.Component {
  492. var copy = self
  493. copy.append(components)
  494. return copy
  495. }
  496. // TODO(Windows docs): example with roots
  497. /// Non-mutating version of `append(_:String)`.
  498. public __consuming func appending(_ other: __owned String) -> FilePath {
  499. var copy = self
  500. copy.append(other)
  501. return copy
  502. }
  503. // TODO(Windows docs): examples and docs with roots, update/generalize doc
  504. // comment
  505. /// If `other` does not have a root, append each component of `other`. If
  506. /// `other` has a root, replaces `self` with other.
  507. ///
  508. /// This operation mimics traversing a directory structure (similar to the
  509. /// `cd` command), where pushing a relative path will append its components
  510. /// and pushing an absolute path will first clear `self`'s existing
  511. /// components.
  512. ///
  513. /// Example:
  514. ///
  515. /// var path: FilePath = "/tmp"
  516. /// path.push("dir/file.txt") // path is "/tmp/dir/file.txt"
  517. /// path.push("/bin") // path is "/bin"
  518. public mutating func push(_ other: __owned FilePath) {
  519. defer { _invariantCheck() }
  520. guard other.root == nil else {
  521. self = other
  522. return
  523. }
  524. // FIXME: Windows drive-relative roots, etc?
  525. _append(unchecked: other._storage[...])
  526. }
  527. // TODO(Windows docs): examples and docs with roots
  528. /// Non-mutating version of `push()`.
  529. public __consuming func pushing(_ other: __owned FilePath) -> FilePath {
  530. var copy = self
  531. copy.push(other)
  532. return copy
  533. }
  534. /// Remove the contents of the path, keeping the null terminator.
  535. public mutating func removeAll(keepingCapacity: Bool = false) {
  536. defer { _invariantCheck() }
  537. _storage.removeAll(keepingCapacity: keepingCapacity)
  538. }
  539. /// Reserve enough storage space to store `minimumCapacity` platform
  540. /// characters.
  541. public mutating func reserveCapacity(_ minimumCapacity: Int) {
  542. defer { _invariantCheck() }
  543. self._storage.reserveCapacity(minimumCapacity)
  544. }
  545. }
  546. // MARK - Renamed
  547. @available(System 0.0.2, *)
  548. extension FilePath {
  549. @available(*, unavailable, renamed: "removingLastComponent()")
  550. public var dirname: FilePath { removingLastComponent() }
  551. @available(*, unavailable, renamed: "lastComponent")
  552. public var basename: Component? { lastComponent }
  553. }