path_segments.rs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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, to_u32, 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. /// # use std::error::Error;
  21. ///
  22. /// # fn run() -> Result<(), Box<Error>> {
  23. /// let mut url = Url::parse("mailto:me@example.com")?;
  24. /// assert!(url.path_segments_mut().is_err());
  25. ///
  26. /// let mut url = Url::parse("http://example.net/foo/index.html")?;
  27. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  28. /// .pop().push("img").push("2/100%.png");
  29. /// assert_eq!(url.as_str(), "http://example.net/foo/img/2%2F100%25.png");
  30. /// # Ok(())
  31. /// # }
  32. /// # run().unwrap();
  33. /// ```
  34. #[derive(Debug)]
  35. pub struct PathSegmentsMut<'a> {
  36. url: &'a mut Url,
  37. after_first_slash: usize,
  38. after_path: String,
  39. old_after_path_position: u32,
  40. }
  41. // Not re-exported outside the crate
  42. pub fn new(url: &mut Url) -> PathSegmentsMut {
  43. let after_path = url.take_after_path();
  44. let old_after_path_position = to_u32(url.serialization.len()).unwrap();
  45. debug_assert!(url.byte_at(url.path_start) == b'/');
  46. PathSegmentsMut {
  47. after_first_slash: url.path_start as usize + "/".len(),
  48. url: url,
  49. old_after_path_position: old_after_path_position,
  50. after_path: after_path,
  51. }
  52. }
  53. impl<'a> Drop for PathSegmentsMut<'a> {
  54. fn drop(&mut self) {
  55. self.url
  56. .restore_after_path(self.old_after_path_position, &self.after_path)
  57. }
  58. }
  59. impl<'a> PathSegmentsMut<'a> {
  60. /// Remove all segments in the path, leaving the minimal `url.path() == "/"`.
  61. ///
  62. /// Returns `&mut Self` so that method calls can be chained.
  63. ///
  64. /// Example:
  65. ///
  66. /// ```rust
  67. /// use url::Url;
  68. /// # use std::error::Error;
  69. ///
  70. /// # fn run() -> Result<(), Box<Error>> {
  71. /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
  72. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  73. /// .clear().push("logout");
  74. /// assert_eq!(url.as_str(), "https://github.com/logout");
  75. /// # Ok(())
  76. /// # }
  77. /// # run().unwrap();
  78. /// ```
  79. pub fn clear(&mut self) -> &mut Self {
  80. self.url.serialization.truncate(self.after_first_slash);
  81. self
  82. }
  83. /// Remove the last segment of this URL’s path if it is empty,
  84. /// except if these was only one segment to begin with.
  85. ///
  86. /// In other words, remove one path trailing slash, if any,
  87. /// unless it is also the initial slash (so this does nothing if `url.path() == "/")`.
  88. ///
  89. /// Returns `&mut Self` so that method calls can be chained.
  90. ///
  91. /// Example:
  92. ///
  93. /// ```rust
  94. /// use url::Url;
  95. /// # use std::error::Error;
  96. ///
  97. /// # fn run() -> Result<(), Box<Error>> {
  98. /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
  99. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  100. /// .push("pulls");
  101. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url//pulls");
  102. ///
  103. /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
  104. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  105. /// .pop_if_empty().push("pulls");
  106. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
  107. /// # Ok(())
  108. /// # }
  109. /// # run().unwrap();
  110. /// ```
  111. pub fn pop_if_empty(&mut self) -> &mut Self {
  112. if self.url.serialization[self.after_first_slash..].ends_with('/') {
  113. self.url.serialization.pop();
  114. }
  115. self
  116. }
  117. /// Remove the last segment of this URL’s path.
  118. ///
  119. /// If the path only has one segment, make it empty such that `url.path() == "/"`.
  120. ///
  121. /// Returns `&mut Self` so that method calls can be chained.
  122. pub fn pop(&mut self) -> &mut Self {
  123. let last_slash = self.url.serialization[self.after_first_slash..]
  124. .rfind('/')
  125. .unwrap_or(0);
  126. self.url
  127. .serialization
  128. .truncate(self.after_first_slash + last_slash);
  129. self
  130. }
  131. /// Append the given segment at the end of this URL’s path.
  132. ///
  133. /// See the documentation for `.extend()`.
  134. ///
  135. /// Returns `&mut Self` so that method calls can be chained.
  136. pub fn push(&mut self, segment: &str) -> &mut Self {
  137. self.extend(Some(segment))
  138. }
  139. /// Append each segment from the given iterator at the end of this URL’s path.
  140. ///
  141. /// Each segment is percent-encoded like in `Url::parse` or `Url::join`,
  142. /// except that `%` and `/` characters are also encoded (to `%25` and `%2F`).
  143. /// This is unlike `Url::parse` where `%` is left as-is in case some of the input
  144. /// is already percent-encoded, and `/` denotes a path segment separator.)
  145. ///
  146. /// Note that, in addition to slashes between new segments,
  147. /// this always adds a slash between the existing path and the new segments
  148. /// *except* if the existing path is `"/"`.
  149. /// If the previous last segment was empty (if the path had a trailing slash)
  150. /// the path after `.extend()` will contain two consecutive slashes.
  151. /// If that is undesired, call `.pop_if_empty()` first.
  152. ///
  153. /// To obtain a behavior similar to `Url::join`, call `.pop()` unconditionally first.
  154. ///
  155. /// Returns `&mut Self` so that method calls can be chained.
  156. ///
  157. /// Example:
  158. ///
  159. /// ```rust
  160. /// use url::Url;
  161. /// # use std::error::Error;
  162. ///
  163. /// # fn run() -> Result<(), Box<Error>> {
  164. /// let mut url = Url::parse("https://github.com/")?;
  165. /// let org = "servo";
  166. /// let repo = "rust-url";
  167. /// let issue_number = "188";
  168. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  169. /// .extend(&[org, repo, "issues", issue_number]);
  170. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/issues/188");
  171. /// # Ok(())
  172. /// # }
  173. /// # run().unwrap();
  174. /// ```
  175. ///
  176. /// In order to make sure that parsing the serialization of an URL gives the same URL,
  177. /// a segment is ignored if it is `"."` or `".."`:
  178. ///
  179. /// ```rust
  180. /// use url::Url;
  181. /// # use std::error::Error;
  182. ///
  183. /// # fn run() -> Result<(), Box<Error>> {
  184. /// let mut url = Url::parse("https://github.com/servo")?;
  185. /// url.path_segments_mut().map_err(|_| "cannot be base")?
  186. /// .extend(&["..", "rust-url", ".", "pulls"]);
  187. /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
  188. /// # Ok(())
  189. /// # }
  190. /// # run().unwrap();
  191. /// ```
  192. pub fn extend<I>(&mut self, segments: I) -> &mut Self
  193. where
  194. I: IntoIterator,
  195. I::Item: AsRef<str>,
  196. {
  197. let scheme_type = SchemeType::from(self.url.scheme());
  198. let path_start = self.url.path_start as usize;
  199. self.url.mutate(|parser| {
  200. parser.context = parser::Context::PathSegmentSetter;
  201. for segment in segments {
  202. let segment = segment.as_ref();
  203. if matches!(segment, "." | "..") {
  204. continue;
  205. }
  206. if parser.serialization.len() > path_start + 1 {
  207. parser.serialization.push('/');
  208. }
  209. let mut has_host = true; // FIXME account for this?
  210. parser.parse_path(
  211. scheme_type,
  212. &mut has_host,
  213. path_start,
  214. parser::Input::new(segment),
  215. );
  216. }
  217. });
  218. self
  219. }
  220. }