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