1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
use std::path::PathBuf; use std::str::Utf8Error; use uri::Uri; /// Iterator over the segments of an absolute URI path. Skips empty segments. /// /// ### Examples /// /// ```rust /// # extern crate rocket; /// use rocket::http::uri::Origin; /// /// let uri = Origin::parse("/a/////b/c////////d").unwrap(); /// let segments = uri.segments(); /// for (i, segment) in segments.enumerate() { /// match i { /// 0 => assert_eq!(segment, "a"), /// 1 => assert_eq!(segment, "b"), /// 2 => assert_eq!(segment, "c"), /// 3 => assert_eq!(segment, "d"), /// _ => panic!("only four segments") /// } /// } /// ``` #[derive(Clone, Debug)] pub struct Segments<'a>(pub &'a str); /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Debug, PartialEq, Eq, Clone)] pub enum SegmentError { /// The segment contained invalid UTF8 characters when percent decoded. Utf8(Utf8Error), /// The segment started with the wrapped invalid character. BadStart(char), /// The segment contained the wrapped invalid character. BadChar(char), /// The segment ended with the wrapped invalid character. BadEnd(char), } impl<'a> Segments<'a> { /// Creates a `PathBuf` from a `Segments` iterator. The returned `PathBuf` /// is percent-decoded. If a segment is equal to "..", the previous segment /// (if any) is skipped. /// /// For security purposes, if a segment meets any of the following /// conditions, an `Err` is returned indicating the condition met: /// /// * Decoded segment starts with any of: '*' /// * Decoded segment ends with any of: `:`, `>`, `<` /// * Decoded segment contains any of: `/` /// * On Windows, decoded segment contains any of: `\` /// * Percent-encoding results in invalid UTF8. /// /// Additionally, if `allow_dotfiles` is `false`, an `Err` is returned if /// the following condition is met: /// /// * Decoded segment starts with any of: `.` (except `..`) /// /// As a result of these conditions, a `PathBuf` derived via `FromSegments` /// is safe to interpolate within, or use as a suffix of, a path without /// additional checks. pub fn into_path_buf(self, allow_dotfiles: bool) -> Result<PathBuf, SegmentError> { let mut buf = PathBuf::new(); for segment in self { let decoded = Uri::percent_decode(segment.as_bytes()) .map_err(SegmentError::Utf8)?; if decoded == ".." { buf.pop(); } else if !allow_dotfiles && decoded.starts_with('.') { return Err(SegmentError::BadStart('.')) } else if decoded.starts_with('*') { return Err(SegmentError::BadStart('*')) } else if decoded.ends_with(':') { return Err(SegmentError::BadEnd(':')) } else if decoded.ends_with('>') { return Err(SegmentError::BadEnd('>')) } else if decoded.ends_with('<') { return Err(SegmentError::BadEnd('<')) } else if decoded.contains('/') { return Err(SegmentError::BadChar('/')) } else if cfg!(windows) && decoded.contains('\\') { return Err(SegmentError::BadChar('\\')) } else { buf.push(&*decoded) } } Ok(buf) } } impl<'a> Iterator for Segments<'a> { type Item = &'a str; #[inline] fn next(&mut self) -> Option<Self::Item> { // Find the start of the next segment (first that's not '/'). let i = self.0.find(|c| c != '/')?; // Get the index of the first character that _is_ a '/' after start. // j = index of first character after i (hence the i +) that's not a '/' let j = self.0[i..].find('/').map_or(self.0.len(), |j| i + j); // Save the result, update the iterator, and return! let result = Some(&self.0[i..j]); self.0 = &self.0[j..]; result } // TODO: Potentially take a second parameter with Option<cached count> and // return it here if it's Some. The downside is that a decision has to be // made about -when- to compute and cache that count. A place to do it is in // the segments() method. But this means that the count will always be // computed regardless of whether it's needed. Maybe this is ok. We'll see. // fn count(self) -> usize where Self: Sized { // self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1)) // } }