diff options
Diffstat (limited to 'third_party/rust/url/src/path_segments.rs')
-rw-r--r-- | third_party/rust/url/src/path_segments.rs | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/third_party/rust/url/src/path_segments.rs b/third_party/rust/url/src/path_segments.rs new file mode 100644 index 0000000000..97055e777c --- /dev/null +++ b/third_party/rust/url/src/path_segments.rs @@ -0,0 +1,229 @@ +// Copyright 2016 The rust-url developers. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use parser::{self, to_u32, SchemeType}; +use std::str; +use Url; + +/// Exposes methods to manipulate the path of an URL that is not cannot-be-base. +/// +/// The path always starts with a `/` slash, and is made of slash-separated segments. +/// There is always at least one segment (which may be the empty string). +/// +/// Examples: +/// +/// ```rust +/// use url::Url; +/// # use std::error::Error; +/// +/// # fn run() -> Result<(), Box<Error>> { +/// let mut url = Url::parse("mailto:me@example.com")?; +/// assert!(url.path_segments_mut().is_err()); +/// +/// let mut url = Url::parse("http://example.net/foo/index.html")?; +/// url.path_segments_mut().map_err(|_| "cannot be base")? +/// .pop().push("img").push("2/100%.png"); +/// assert_eq!(url.as_str(), "http://example.net/foo/img/2%2F100%25.png"); +/// # Ok(()) +/// # } +/// # run().unwrap(); +/// ``` +#[derive(Debug)] +pub struct PathSegmentsMut<'a> { + url: &'a mut Url, + after_first_slash: usize, + after_path: String, + old_after_path_position: u32, +} + +// Not re-exported outside the crate +pub fn new(url: &mut Url) -> PathSegmentsMut { + let after_path = url.take_after_path(); + let old_after_path_position = to_u32(url.serialization.len()).unwrap(); + debug_assert!(url.byte_at(url.path_start) == b'/'); + PathSegmentsMut { + after_first_slash: url.path_start as usize + "/".len(), + url, + old_after_path_position, + after_path, + } +} + +impl<'a> Drop for PathSegmentsMut<'a> { + fn drop(&mut self) { + self.url + .restore_after_path(self.old_after_path_position, &self.after_path) + } +} + +impl<'a> PathSegmentsMut<'a> { + /// Remove all segments in the path, leaving the minimal `url.path() == "/"`. + /// + /// Returns `&mut Self` so that method calls can be chained. + /// + /// Example: + /// + /// ```rust + /// use url::Url; + /// # use std::error::Error; + /// + /// # fn run() -> Result<(), Box<Error>> { + /// let mut url = Url::parse("https://github.com/servo/rust-url/")?; + /// url.path_segments_mut().map_err(|_| "cannot be base")? + /// .clear().push("logout"); + /// assert_eq!(url.as_str(), "https://github.com/logout"); + /// # Ok(()) + /// # } + /// # run().unwrap(); + /// ``` + pub fn clear(&mut self) -> &mut Self { + self.url.serialization.truncate(self.after_first_slash); + self + } + + /// Remove the last segment of this URL’s path if it is empty, + /// except if these was only one segment to begin with. + /// + /// In other words, remove one path trailing slash, if any, + /// unless it is also the initial slash (so this does nothing if `url.path() == "/")`. + /// + /// Returns `&mut Self` so that method calls can be chained. + /// + /// Example: + /// + /// ```rust + /// use url::Url; + /// # use std::error::Error; + /// + /// # fn run() -> Result<(), Box<Error>> { + /// let mut url = Url::parse("https://github.com/servo/rust-url/")?; + /// url.path_segments_mut().map_err(|_| "cannot be base")? + /// .push("pulls"); + /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url//pulls"); + /// + /// let mut url = Url::parse("https://github.com/servo/rust-url/")?; + /// url.path_segments_mut().map_err(|_| "cannot be base")? + /// .pop_if_empty().push("pulls"); + /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls"); + /// # Ok(()) + /// # } + /// # run().unwrap(); + /// ``` + pub fn pop_if_empty(&mut self) -> &mut Self { + if self.url.serialization[self.after_first_slash..].ends_with('/') { + self.url.serialization.pop(); + } + self + } + + /// Remove the last segment of this URL’s path. + /// + /// If the path only has one segment, make it empty such that `url.path() == "/"`. + /// + /// Returns `&mut Self` so that method calls can be chained. + pub fn pop(&mut self) -> &mut Self { + let last_slash = self.url.serialization[self.after_first_slash..] + .rfind('/') + .unwrap_or(0); + self.url + .serialization + .truncate(self.after_first_slash + last_slash); + self + } + + /// Append the given segment at the end of this URL’s path. + /// + /// See the documentation for `.extend()`. + /// + /// Returns `&mut Self` so that method calls can be chained. + pub fn push(&mut self, segment: &str) -> &mut Self { + self.extend(Some(segment)) + } + + /// Append each segment from the given iterator at the end of this URL’s path. + /// + /// Each segment is percent-encoded like in `Url::parse` or `Url::join`, + /// except that `%` and `/` characters are also encoded (to `%25` and `%2F`). + /// This is unlike `Url::parse` where `%` is left as-is in case some of the input + /// is already percent-encoded, and `/` denotes a path segment separator.) + /// + /// Note that, in addition to slashes between new segments, + /// this always adds a slash between the existing path and the new segments + /// *except* if the existing path is `"/"`. + /// If the previous last segment was empty (if the path had a trailing slash) + /// the path after `.extend()` will contain two consecutive slashes. + /// If that is undesired, call `.pop_if_empty()` first. + /// + /// To obtain a behavior similar to `Url::join`, call `.pop()` unconditionally first. + /// + /// Returns `&mut Self` so that method calls can be chained. + /// + /// Example: + /// + /// ```rust + /// use url::Url; + /// # use std::error::Error; + /// + /// # fn run() -> Result<(), Box<Error>> { + /// let mut url = Url::parse("https://github.com/")?; + /// let org = "servo"; + /// let repo = "rust-url"; + /// let issue_number = "188"; + /// url.path_segments_mut().map_err(|_| "cannot be base")? + /// .extend(&[org, repo, "issues", issue_number]); + /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/issues/188"); + /// # Ok(()) + /// # } + /// # run().unwrap(); + /// ``` + /// + /// In order to make sure that parsing the serialization of an URL gives the same URL, + /// a segment is ignored if it is `"."` or `".."`: + /// + /// ```rust + /// use url::Url; + /// # use std::error::Error; + /// + /// # fn run() -> Result<(), Box<Error>> { + /// let mut url = Url::parse("https://github.com/servo")?; + /// url.path_segments_mut().map_err(|_| "cannot be base")? + /// .extend(&["..", "rust-url", ".", "pulls"]); + /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls"); + /// # Ok(()) + /// # } + /// # run().unwrap(); + /// ``` + pub fn extend<I>(&mut self, segments: I) -> &mut Self + where + I: IntoIterator, + I::Item: AsRef<str>, + { + let scheme_type = SchemeType::from(self.url.scheme()); + let path_start = self.url.path_start as usize; + self.url.mutate(|parser| { + parser.context = parser::Context::PathSegmentSetter; + for segment in segments { + let segment = segment.as_ref(); + if matches!(segment, "." | "..") { + continue; + } + if parser.serialization.len() > path_start + 1 { + parser.serialization.push('/'); + } + let mut has_host = true; // FIXME account for this? + parser.parse_path( + scheme_type, + &mut has_host, + path_start, + parser::Input::new(segment), + ); + } + }); + self + } +} |