Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 651c202060d2ecedcb8ee3733148b47688df81d4
parent 948a9e6720d99ec9f5487a3320916de6f8fadce0
Author: Sergio Benitez <sb@sergio.bz>
Date:   Mon, 13 Aug 2018 00:46:31 -0700

Move 'SegmentError' into 'uri' module.

Diffstat:
Mcore/codegen/tests/segments.rs | 2+-
Mcore/http/src/uri/mod.rs | 2++
Mcore/http/src/uri/origin.rs | 53+----------------------------------------------------
Acore/http/src/uri/segments.rs | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/lib/src/request/mod.rs | 2+-
Mcore/lib/src/request/param.rs | 46+++-------------------------------------------
6 files changed, 131 insertions(+), 97 deletions(-)

diff --git a/core/codegen/tests/segments.rs b/core/codegen/tests/segments.rs @@ -4,7 +4,7 @@ extern crate rocket; use std::path::PathBuf; -use rocket::request::SegmentError; +use rocket::http::uri::SegmentError; #[post("/<a>/<b..>")] fn get(a: String, b: PathBuf) -> String { diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs @@ -6,6 +6,7 @@ mod from_uri_param; mod origin; mod authority; mod absolute; +mod segments; pub use parse::uri::Error; @@ -15,3 +16,4 @@ pub use self::origin::*; pub use self::absolute::*; pub use self::uri_display::*; pub use self::from_uri_param::*; +pub use self::segments::*; diff --git a/core/http/src/uri/origin.rs b/core/http/src/uri/origin.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use ext::IntoOwned; use parse::{Indexed, IndexedStr}; -use uri::{as_utf8_unchecked, Error}; +use uri::{as_utf8_unchecked, Error, Segments}; use state::Storage; @@ -451,57 +451,6 @@ impl<'a> Display for Origin<'a> { } } -/// 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); - -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)) - // } -} - #[cfg(test)] mod tests { use super::Origin; diff --git a/core/http/src/uri/segments.rs b/core/http/src/uri/segments.rs @@ -0,0 +1,123 @@ +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)) + // } +} + diff --git a/core/lib/src/request/mod.rs b/core/lib/src/request/mod.rs @@ -11,7 +11,7 @@ mod tests; pub use self::request::Request; pub use self::from_request::{FromRequest, Outcome}; -pub use self::param::{FromParam, FromSegments, SegmentError}; +pub use self::param::{FromParam, FromSegments}; pub use self::form::{Form, FormError, LenientForm, FromForm, FromFormValue, FormItems}; pub use self::state::State; diff --git a/core/lib/src/request/param.rs b/core/lib/src/request/param.rs @@ -1,9 +1,9 @@ -use std::str::{FromStr, Utf8Error}; +use std::str::FromStr; use std::path::PathBuf; use std::fmt::Debug; use std::borrow::Cow; -use http::{RawStr, uri::{Uri, Segments}}; +use http::{RawStr, uri::{Segments, SegmentError}}; /// Trait to convert a dynamic path segment string to a concrete value. /// @@ -308,20 +308,6 @@ impl<'a> FromSegments<'a> for Segments<'a> { } } -/// 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), -} - /// 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. @@ -342,33 +328,7 @@ impl<'a> FromSegments<'a> for PathBuf { type Error = SegmentError; fn from_segments(segments: Segments<'a>) -> Result<PathBuf, SegmentError> { - let mut buf = PathBuf::new(); - for segment in segments { - let decoded = Uri::percent_decode(segment.as_bytes()) - .map_err(SegmentError::Utf8)?; - - if decoded == ".." { - buf.pop(); - } else if 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) + segments.into_path_buf(false) } }