rocket_http/uri/
segments.rs

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