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