rocket_http_community/uri/segments.rs
1use std::path::PathBuf;
2
3use crate::uri::error::PathError;
4use crate::uri::fmt::{Part, Path, Query};
5use crate::RawStr;
6
7/// Iterator over the non-empty, percent-decoded segments of a URI component.
8///
9/// Returned by [`Path::segments()`] and [`Query::segments()`].
10///
11/// [`Path::segments()`]: crate::uri::Path::segments()
12/// [`Query::segments()`]: crate::uri::Query::segments()
13///
14/// # Example
15///
16/// ```rust
17/// # extern crate rocket;
18/// use rocket::http::uri::Origin;
19///
20/// let uri = Origin::parse("/a%20z/////b/c////////d").unwrap();
21/// let segments = uri.path().segments();
22/// for (i, segment) in segments.enumerate() {
23/// match i {
24/// 0 => assert_eq!(segment, "a z"),
25/// 1 => assert_eq!(segment, "b"),
26/// 2 => assert_eq!(segment, "c"),
27/// 3 => assert_eq!(segment, "d"),
28/// _ => panic!("only four segments")
29/// }
30/// }
31/// # assert_eq!(uri.path().segments().num(), 4);
32/// # assert_eq!(uri.path().segments().count(), 4);
33/// # assert_eq!(uri.path().segments().next(), Some("a z"));
34/// ```
35#[derive(Debug, Clone)]
36pub struct Segments<'a, P: Part> {
37 pub(super) source: &'a RawStr,
38 pub(super) segments: &'a [P::Raw],
39 pub(super) pos: usize,
40}
41
42impl<P: Part> Segments<'_, P> {
43 #[doc(hidden)]
44 #[inline(always)]
45 pub fn new<'a>(source: &'a RawStr, segments: &'a [P::Raw]) -> Segments<'a, P> {
46 Segments {
47 source,
48 segments,
49 pos: 0,
50 }
51 }
52
53 /// Returns the number of path segments left.
54 ///
55 /// # Example
56 ///
57 /// ```rust
58 /// # #[macro_use] extern crate rocket;
59 /// let uri = uri!("/foo/bar?baz&cat&car");
60 ///
61 /// let mut segments = uri.path().segments();
62 /// assert_eq!(segments.num(), 2);
63 ///
64 /// segments.next();
65 /// assert_eq!(segments.num(), 1);
66 ///
67 /// segments.next();
68 /// assert_eq!(segments.num(), 0);
69 ///
70 /// segments.next();
71 /// assert_eq!(segments.num(), 0);
72 /// ```
73 #[inline]
74 pub fn num(&self) -> usize {
75 let max_pos = std::cmp::min(self.pos, self.segments.len());
76 self.segments.len() - max_pos
77 }
78
79 /// Returns `true` if there are no segments left.
80 ///
81 /// # Example
82 ///
83 /// ```rust
84 /// # #[macro_use] extern crate rocket;
85 /// let uri = uri!("/foo/bar?baz&cat&car");
86 ///
87 /// let mut segments = uri.path().segments();
88 /// assert!(!segments.is_empty());
89 ///
90 /// segments.next();
91 /// segments.next();
92 /// assert!(segments.is_empty());
93 /// ```
94 #[inline]
95 pub fn is_empty(&self) -> bool {
96 self.num() == 0
97 }
98
99 /// Returns a new `Segments` with `n` segments skipped.
100 ///
101 /// # Example
102 ///
103 /// ```rust
104 /// # #[macro_use] extern crate rocket;
105 /// let uri = uri!("/foo/bar/baz/cat");
106 ///
107 /// let mut segments = uri.path().segments();
108 /// assert_eq!(segments.num(), 4);
109 /// assert_eq!(segments.next(), Some("foo"));
110 ///
111 /// let mut segments = segments.skip(2);
112 /// assert_eq!(segments.num(), 1);
113 /// assert_eq!(segments.next(), Some("cat"));
114 /// ```
115 #[inline]
116 pub fn skip(mut self, n: usize) -> Self {
117 self.pos = std::cmp::min(self.pos + n, self.segments.len());
118 self
119 }
120}
121
122impl<'a> Segments<'a, Path> {
123 /// Returns the `n`th segment, 0-indexed, from the current position.
124 ///
125 /// # Example
126 ///
127 /// ```rust
128 /// # #[macro_use] extern crate rocket;
129 /// let uri = uri!("/foo/bar/baaaz/cat");
130 ///
131 /// let segments = uri.path().segments();
132 /// assert_eq!(segments.get(0), Some("foo"));
133 /// assert_eq!(segments.get(1), Some("bar"));
134 /// assert_eq!(segments.get(2), Some("baaaz"));
135 /// assert_eq!(segments.get(3), Some("cat"));
136 /// assert_eq!(segments.get(4), None);
137 /// ```
138 #[inline]
139 pub fn get(&self, n: usize) -> Option<&'a str> {
140 self.segments
141 .get(self.pos + n)
142 .map(|i| i.from_source(Some(self.source.as_str())))
143 }
144
145 /// Returns `true` if `self` is a prefix of `other`.
146 ///
147 /// # Example
148 ///
149 /// ```rust
150 /// # #[macro_use] extern crate rocket;
151 /// let a = uri!("/");
152 /// let b = uri!("/");
153 /// assert!(a.path().segments().prefix_of(b.path().segments()));
154 /// assert!(b.path().segments().prefix_of(a.path().segments()));
155 ///
156 /// let a = uri!("/");
157 /// let b = uri!("/foo");
158 /// assert!(a.path().segments().prefix_of(b.path().segments()));
159 /// assert!(!b.path().segments().prefix_of(a.path().segments()));
160 ///
161 /// let a = uri!("/foo");
162 /// let b = uri!("/foo/");
163 /// assert!(a.path().segments().prefix_of(b.path().segments()));
164 /// assert!(!b.path().segments().prefix_of(a.path().segments()));
165 ///
166 /// let a = uri!("/foo/bar/baaaz/cat");
167 /// let b = uri!("/foo/bar");
168 ///
169 /// assert!(b.path().segments().prefix_of(a.path().segments()));
170 /// assert!(!a.path().segments().prefix_of(b.path().segments()));
171 ///
172 /// let a = uri!("/foo/bar/baaaz/cat");
173 /// let b = uri!("/dog/foo/bar");
174 /// assert!(b.path().segments().skip(1).prefix_of(a.path().segments()));
175 /// ```
176 #[inline]
177 pub fn prefix_of(self, other: Segments<'_, Path>) -> bool {
178 if self.num() > other.num() {
179 return false;
180 }
181
182 self.zip(other).all(|(a, b)| a.is_empty() || a == b)
183 }
184
185 /// Creates a `PathBuf` from `self`. The returned `PathBuf` is
186 /// percent-decoded and guaranteed to be relative. If a segment is equal to
187 /// `.`, it is skipped. If a segment is equal to `..`, the previous segment
188 /// (if any) is skipped.
189 ///
190 /// For security purposes, if a segment meets any of the following
191 /// conditions, an `Err` is returned indicating the condition met:
192 ///
193 /// * Decoded segment starts with any of: `*`
194 /// * Decoded segment ends with any of: `:`, `>`, `<`
195 /// * Decoded segment contains any of: `/`
196 /// * On Windows, decoded segment contains any of: `\`, `:`
197 /// * Percent-encoding results in invalid UTF-8.
198 ///
199 /// Additionally, if `allow_dotfiles` is `false`, an `Err` is returned if
200 /// the following condition is met:
201 ///
202 /// * Decoded segment starts with any of: `.` (except `..` and `.`)
203 ///
204 /// As a result of these conditions, a `PathBuf` derived via `FromSegments`
205 /// is safe to interpolate within, or use as a suffix of, a path without
206 /// additional checks.
207 ///
208 /// # Example
209 ///
210 /// ```rust
211 /// # #[macro_use] extern crate rocket;
212 /// use std::path::Path;
213 ///
214 /// let uri = uri!("/a/b/c/d/.pass");
215 ///
216 /// let path = uri.path().segments().to_path_buf(true);
217 /// assert_eq!(path.unwrap(), Path::new("a/b/c/d/.pass"));
218 ///
219 /// let path = uri.path().segments().to_path_buf(false);
220 /// assert!(path.is_err());
221 /// ```
222 pub fn to_path_buf(&self, allow_dotfiles: bool) -> Result<PathBuf, PathError> {
223 let mut buf = PathBuf::new();
224 for segment in self.clone() {
225 if segment == "." {
226 continue;
227 } else if segment == ".." {
228 buf.pop();
229 } else if !allow_dotfiles && segment.starts_with('.') {
230 return Err(PathError::BadStart('.'));
231 } else if segment.starts_with('*') {
232 return Err(PathError::BadStart('*'));
233 } else if segment.ends_with(':') {
234 return Err(PathError::BadEnd(':'));
235 } else if segment.ends_with('>') {
236 return Err(PathError::BadEnd('>'));
237 } else if segment.ends_with('<') {
238 return Err(PathError::BadEnd('<'));
239 } else if segment.contains('/') {
240 return Err(PathError::BadChar('/'));
241 } else if cfg!(windows) && segment.contains('\\') {
242 return Err(PathError::BadChar('\\'));
243 } else if cfg!(windows) && segment.contains(':') {
244 return Err(PathError::BadChar(':'));
245 } else {
246 buf.push(segment)
247 }
248 }
249
250 // TODO: Should we check the filename against the list in `FileName`?
251 // That list is mostly for writing, while this is likely to be read.
252 // TODO: Add an option to allow/disallow shell characters?
253
254 Ok(buf)
255 }
256}
257
258impl<'a> Segments<'a, Query> {
259 /// Returns the `n`th segment, 0-indexed, from the current position.
260 ///
261 /// # Example
262 ///
263 /// ```rust
264 /// # #[macro_use] extern crate rocket;
265 /// let uri = uri!("/?foo=1&bar=hi+there&baaaz&cat=dog&=oh");
266 ///
267 /// let segments = uri.query().unwrap().segments();
268 /// assert_eq!(segments.get(0), Some(("foo", "1")));
269 /// assert_eq!(segments.get(1), Some(("bar", "hi there")));
270 /// assert_eq!(segments.get(2), Some(("baaaz", "")));
271 /// assert_eq!(segments.get(3), Some(("cat", "dog")));
272 /// assert_eq!(segments.get(4), Some(("", "oh")));
273 /// assert_eq!(segments.get(5), None);
274 /// ```
275 #[inline]
276 pub fn get(&self, n: usize) -> Option<(&'a str, &'a str)> {
277 let (name, val) = self.segments.get(self.pos + n)?;
278 let source = Some(self.source.as_str());
279 let name = name.from_source(source);
280 let val = val.from_source(source);
281 Some((name, val))
282 }
283}
284
285macro_rules! impl_iterator {
286 ($T:ty => $I:ty) => {
287 impl<'a> Iterator for Segments<'a, $T> {
288 type Item = $I;
289
290 fn next(&mut self) -> Option<Self::Item> {
291 let item = self.get(0)?;
292 self.pos += 1;
293 Some(item)
294 }
295
296 fn size_hint(&self) -> (usize, Option<usize>) {
297 (self.num(), Some(self.num()))
298 }
299
300 fn count(self) -> usize {
301 self.num()
302 }
303 }
304 };
305}
306
307impl_iterator!(Path => &'a str);
308impl_iterator!(Query => (&'a str, &'a str));