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