path_segments.rs 9.0 KB

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