path_segments.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2016 The rust-url developers.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. use parser::{self, SchemeType};
  9. use std::str;
  10. use Url;
  11. /// Exposes methods to manipulate the path of an URL that is not cannot-be-base.
  12. ///
  13. /// The path always starts with a `/` slash, and is made of slash-separated segments.
  14. /// There is always at least one segment (which may be the empty string).
  15. ///
  16. /// Examples:
  17. ///
  18. /// ```rust
  19. /// # use url::Url;
  20. /// let mut url = Url::parse("mailto:me@example.com").unwrap();
  21. /// assert!(url.path_segments_mut().is_err());
  22. ///
  23. /// let mut url = Url::parse("http://example.net/foo/index.html").unwrap();
  24. /// url.path_segments_mut().unwrap().pop().push("img").push("2/100%.png");
  25. /// assert_eq!(url.as_str(), "http://example.net/foo/img/2%2F100%25.png");
  26. /// ```
  27. pub struct PathSegmentsMut<'a> {
  28. url: &'a mut Url,
  29. after_first_slash: usize,
  30. after_path: String,
  31. old_after_path_position: u32,
  32. }
  33. // Not re-exported outside the crate
  34. pub fn new(url: &mut Url) -> PathSegmentsMut {
  35. let (old_after_path_position, after_path) = url.take_after_path();
  36. debug_assert!(url.byte_at(url.path_start) == b'/');
  37. PathSegmentsMut {
  38. after_first_slash: url.path_start as usize + "/".len(),
  39. url: url,
  40. old_after_path_position: old_after_path_position,
  41. after_path: after_path,
  42. }
  43. }
  44. impl<'a> Drop for PathSegmentsMut<'a> {
  45. fn drop(&mut self) {
  46. self.url.restore_after_path(self.old_after_path_position, &self.after_path)
  47. }
  48. }
  49. impl<'a> PathSegmentsMut<'a> {
  50. /// Remove all segments in the path, leaving the minimal `url.path() == "/"`.
  51. ///
  52. /// Returns `&mut Self` so that method calls can be chained.
  53. ///
  54. /// Example:
  55. ///
  56. /// ```rust
  57. /// # use url::Url;
  58. /// let mut url = Url::parse("https://github.com/servo/rust-url/").unwrap();
  59. /// url.path_segments_mut().unwrap().clear().push("logout");
  60. /// assert_eq!(url.as_str(), "https://github.com/logout");
  61. /// ```
  62. pub fn clear(&mut self) -> &mut Self {
  63. self.url.serialization.truncate(self.after_first_slash);
  64. self
  65. }
  66. /// Remove the last segment of this URL’s path if it is empty,
  67. /// except if these was only one segment to begin with.
  68. ///
  69. /// In other words, remove one path trailing slash, if any,
  70. /// unless it is also the initial slash (so this does nothing if `url.path() == "/")`.
  71. ///
  72. /// Returns `&mut Self` so that method calls can be chained.
  73. ///
  74. /// Example:
  75. ///
  76. /// ```rust
  77. /// # use url::Url;
  78. /// let mut url = Url::parse("https://github.com/servo/rust-url/").unwrap();
  79. /// url.path_segments_mut().unwrap().push("pulls");
  80. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url//pulls");
  81. ///
  82. /// let mut url = Url::parse("https://github.com/servo/rust-url/").unwrap();
  83. /// url.path_segments_mut().unwrap().pop_if_empty().push("pulls");
  84. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
  85. /// ```
  86. pub fn pop_if_empty(&mut self) -> &mut Self {
  87. if self.url.serialization[self.after_first_slash..].ends_with('/') {
  88. self.url.serialization.pop();
  89. }
  90. self
  91. }
  92. /// Remove the last segment of this URL’s path.
  93. ///
  94. /// If the path only has one segment, make it empty such that `url.path() == "/"`.
  95. ///
  96. /// Returns `&mut Self` so that method calls can be chained.
  97. pub fn pop(&mut self) -> &mut Self {
  98. let last_slash = self.url.serialization[self.after_first_slash..].rfind('/').unwrap_or(0);
  99. self.url.serialization.truncate(self.after_first_slash + last_slash);
  100. self
  101. }
  102. /// Append the given segment at the end of this URL’s path.
  103. ///
  104. /// See the documentation for `.extend()`.
  105. ///
  106. /// Returns `&mut Self` so that method calls can be chained.
  107. pub fn push(&mut self, segment: &str) -> &mut Self {
  108. self.extend(Some(segment))
  109. }
  110. /// Append each segment from the given iterator at the end of this URL’s path.
  111. ///
  112. /// Each segment is percent-encoded like in `Url::parse` or `Url::join`,
  113. /// except that `%` and `/` characters are also encoded (to `%25` and `%2F`).
  114. /// This is unlike `Url::parse` where `%` is left as-is in case some of the input
  115. /// is already percent-encoded, and `/` denotes a path segment separator.)
  116. ///
  117. /// Note that, in addition to slashes between new segments,
  118. /// this always adds a slash between the existing path and the new segments
  119. /// *except* if the existing path is `"/"`.
  120. /// If the previous last segment was empty (if the path had a trailing slash)
  121. /// the path after `.extend()` will contain two consecutive slashes.
  122. /// If that is undesired, call `.pop_if_empty()` first.
  123. ///
  124. /// To obtain a behavior similar to `Url::join`, call `.pop()` unconditionally first.
  125. ///
  126. /// Returns `&mut Self` so that method calls can be chained.
  127. ///
  128. /// Example:
  129. ///
  130. /// ```rust
  131. /// # use url::Url;
  132. /// let mut url = Url::parse("https://github.com/").unwrap();
  133. /// let org = "servo";
  134. /// let repo = "rust-url";
  135. /// let issue_number = "188";
  136. /// url.path_segments_mut().unwrap().extend(&[org, repo, "issues", issue_number]);
  137. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/issues/188");
  138. /// ```
  139. ///
  140. /// In order to make sure that parsing the serialization of an URL gives the same URL,
  141. /// a segment is ignored if it is `"."` or `".."`:
  142. ///
  143. /// ```rust
  144. /// # use url::Url;
  145. /// let mut url = Url::parse("https://github.com/servo").unwrap();
  146. /// url.path_segments_mut().unwrap().extend(&["..", "rust-url", ".", "pulls"]);
  147. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
  148. /// ```
  149. pub fn extend<I>(&mut self, segments: I) -> &mut Self
  150. where I: IntoIterator, I::Item: AsRef<str> {
  151. let scheme_type = SchemeType::from(self.url.scheme());
  152. let path_start = self.url.path_start as usize;
  153. self.url.mutate(|parser| {
  154. parser.context = parser::Context::PathSegmentSetter;
  155. for segment in segments {
  156. let segment = segment.as_ref();
  157. if matches!(segment, "." | "..") {
  158. continue
  159. }
  160. if parser.serialization.len() > path_start + 1 {
  161. parser.serialization.push('/');
  162. }
  163. let mut has_host = true; // FIXME account for this?
  164. parser.parse_path(scheme_type, &mut has_host, path_start,
  165. parser::Input::new(segment));
  166. }
  167. });
  168. self
  169. }
  170. /// For internal testing, not part of the public API.
  171. #[doc(hidden)]
  172. pub fn assert_url_invariants(&mut self) -> &mut Self {
  173. self.url.assert_invariants();
  174. self
  175. }
  176. }