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));