From 579508d58f0c8b240da0ea4f7cb74eb954dc4250 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 3 Apr 2021 17:06:23 -0700 Subject: [PATCH] Make 'FileName' a DST. Improve sanitization. Resolves #1594. --- core/codegen/tests/from_form.rs | 2 +- core/lib/Cargo.toml | 1 - core/lib/src/data/temp_file.rs | 50 +- core/lib/src/form/field.rs | 60 +- core/lib/src/form/name.rs | 950 ---------------------------- core/lib/src/form/name/buf.rs | 223 +++++++ core/lib/src/form/name/file_name.rs | 215 +++++++ core/lib/src/form/name/key.rs | 147 +++++ core/lib/src/form/name/mod.rs | 13 + core/lib/src/form/name/name.rs | 232 +++++++ core/lib/src/form/name/view.rs | 357 +++++++++++ core/lib/src/form/parser.rs | 2 +- scripts/test.sh | 1 - 13 files changed, 1225 insertions(+), 1028 deletions(-) delete mode 100644 core/lib/src/form/name.rs create mode 100644 core/lib/src/form/name/buf.rs create mode 100644 core/lib/src/form/name/file_name.rs create mode 100644 core/lib/src/form/name/key.rs create mode 100644 core/lib/src/form/name/mod.rs create mode 100644 core/lib/src/form/name/name.rs create mode 100644 core/lib/src/form/name/view.rs diff --git a/core/codegen/tests/from_form.rs b/core/codegen/tests/from_form.rs index 5062d682..2b6c65df 100644 --- a/core/codegen/tests/from_form.rs +++ b/core/codegen/tests/from_form.rs @@ -579,7 +579,7 @@ fn test_multipart() { #[rocket::post("/", data = "
")] fn form(form: Form) { assert_eq!(form.names, &["abcd", "123"]); - assert_eq!(form.file.file_name(), Some("foo")); + assert_eq!(form.file.name(), Some("foo")); } let client = Client::debug_with(rocket::routes![form]).unwrap(); diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index 510e3d56..8d73ae9d 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -22,7 +22,6 @@ all-features = true default = [] tls = ["rocket_http/tls"] secrets = ["rocket_http/private-cookies"] -trusted_client = [] [dependencies] futures = "0.3.0" diff --git a/core/lib/src/data/temp_file.rs b/core/lib/src/data/temp_file.rs index b6da7fce..6b5355cf 100644 --- a/core/lib/src/data/temp_file.rs +++ b/core/lib/src/data/temp_file.rs @@ -3,7 +3,7 @@ use std::path::{PathBuf, Path}; use crate::http::{ContentType, Status}; use crate::data::{FromData, Data, Capped, N, Limits}; -use crate::form::{FromFormField, ValueField, DataField, error::Errors}; +use crate::form::{FromFormField, ValueField, DataField, error::Errors, name::FileName}; use crate::outcome::IntoOutcome; use crate::request::Request; @@ -101,7 +101,7 @@ use either::Either; pub enum TempFile<'v> { #[doc(hidden)] File { - file_name: Option<&'v str>, + file_name: Option<&'v FileName>, content_type: Option, path: Either, len: u64, @@ -363,17 +363,20 @@ impl<'v> TempFile<'v> { } } - /// Returns the name of the file as specified in the form field. + /// Returns the sanitized file name as specified in the form field. /// - /// A multipart data form field can optionally specify the name of a file. - /// A browser will typically send the actual name of a user's selected file - /// in this field. This method returns that value, if it was specified, - /// without a file extension. + /// A multipart data form field can optionally specify the name of a file. A + /// browser will typically send the actual name of a user's selected file in + /// this field, but clients are also able to specify _any_ name, including + /// invalid or dangerous file names. This method returns a sanitized version + /// of that value, if it was specified, suitable and safe for use as a + /// permanent file name. /// - /// The name is guaranteed to be a _true_ filename minus the extension. It - /// has been sanitized so as to not to contain path components, start with - /// `.` or `*`, or end with `:`, `>`, or `<`, making it safe for direct use - /// as the name of a file. + /// Note that you will likely want to prepend or append random or + /// user-specific components to the name to avoid collisions; UUIDs make for + /// a good "random" data. + /// + /// See [`FileName::as_str()`] for specifics on sanitization. /// /// ```rust /// # #[macro_use] extern crate rocket; @@ -382,15 +385,30 @@ impl<'v> TempFile<'v> { /// #[post("/", data = "")] /// async fn handle(mut file: TempFile<'_>) -> std::io::Result<()> { /// # let some_dir = std::env::temp_dir(); - /// if let Some(name) = file.file_name() { - /// // Due to Rocket's sanitization, this is safe. + /// if let Some(name) = file.name() { + /// // Because of Rocket's sanitization, this is safe. /// file.persist_to(&some_dir.join(name)).await?; /// } /// /// Ok(()) /// } /// ``` - pub fn file_name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { + self.raw_name().and_then(|f| f.as_str()) + } + + /// Returns the raw name of the file as specified in the form field. + /// + /// ```rust + /// # #[macro_use] extern crate rocket; + /// use rocket::data::TempFile; + /// + /// #[post("/", data = "")] + /// async fn handle(mut file: TempFile<'_>) { + /// let raw_name = file.raw_name(); + /// } + /// ``` + pub fn raw_name(&self) -> Option<&FileName> { match *self { TempFile::File { file_name, .. } => file_name, TempFile::Buffered { .. } => None @@ -422,7 +440,7 @@ impl<'v> TempFile<'v> { async fn from<'a>( req: &Request<'_>, data: Data, - file_name: Option<&'a str>, + file_name: Option<&'a FileName>, content_type: Option, ) -> io::Result>> { let limit = content_type.as_ref() @@ -461,7 +479,7 @@ impl<'v> FromFormField<'v> for Capped> { async fn from_data( f: DataField<'v, '_> ) -> Result> { - Ok(TempFile::from(f.request, f.data, f.file_name.and_then(|f| f.name()), Some(f.content_type)).await?) + Ok(TempFile::from(f.request, f.data, f.file_name, Some(f.content_type)).await?) } } diff --git a/core/lib/src/form/field.rs b/core/lib/src/form/field.rs index 3953f72f..0f887538 100644 --- a/core/lib/src/form/field.rs +++ b/core/lib/src/form/field.rs @@ -1,4 +1,4 @@ -use crate::form::name::NameView; +use crate::form::name::{NameView, FileName}; use crate::form::error::{Error, ErrorKind, Entity}; use crate::http::{ContentType, RawStr}; use crate::{Request, Data}; @@ -16,62 +16,6 @@ pub struct ValueField<'r> { pub value: &'r str, } - -/// A file name found in a multipart field -pub struct FileName<'r> { - raw_name: &'r str -} - -impl<'r> FileName<'r> { - pub(crate) fn new(raw_name: &'r str) -> FileName<'r> { - FileName { - raw_name - } - } - - /// The form field's sanitized file name. - /// - /// ## Santization - /// - /// A "sanitized" file name is a non-empty string, stripped of its file - /// exntesion, which does not contain any path delimiters (`/` or `\`), is - /// not a hidden (`.`) or `*`-prefixed name, and does not contain special - /// characters (`:`, `>`, or `<`). If the submitted file name matches any of - /// these properties or none was submitted, the return value will be `None`. - pub fn name(&self) -> Option<&'r str> { - fn sanitize(file_name: &str) -> Option<&str> { - let file_name = std::path::Path::new(file_name) - .file_name() - .and_then(|n| n.to_str()) - .map(|n| n.find('.').map(|i| n.split_at(i).0).unwrap_or(n))?; - - if file_name.is_empty() - || file_name.starts_with(|c| c == '.' || c == '*') - || file_name.ends_with(|c| c == ':' || c == '>' || c == '<') - || file_name.contains(|c| c == '/' || c == '\\') - { - return None - } - - Some(file_name) - } - - sanitize(self.raw_name) - } - - /// The raw file name. The client can send arbitrary text here. - /// In almost every case, use name() instead, which returns a safe sanitized file name. - /// Only use this if the server is not publicly accessible and - /// you can trust the client to send correct data **and** you really need the orinal file name. - /// Don't rely on the file extension to figure out the type of data. - /// - /// Note: This function requires the "trusted_client" feature to be enabled. - #[cfg(feature = "trusted_client")] - pub fn raw_name(&self) -> &'r str { - self.raw_name - } -} - /// A multipart form field with an underlying data stream. /// /// Rocket preprocesses all form fields into either [`ValueField`]s or @@ -82,7 +26,7 @@ pub struct DataField<'r, 'i> { /// The (decoded) name of the form field. pub name: NameView<'r>, /// The form fields's file name. - pub file_name: Option>, + pub file_name: Option<&'r FileName>, /// The form field's Content-Type, as submitted, which may or may not /// reflect on `data`. pub content_type: ContentType, diff --git a/core/lib/src/form/name.rs b/core/lib/src/form/name.rs deleted file mode 100644 index 54635415..00000000 --- a/core/lib/src/form/name.rs +++ /dev/null @@ -1,950 +0,0 @@ -//! Types for handling field names, name keys, and key indices. - -use std::ops::Deref; -use std::borrow::Cow; - -use ref_cast::RefCast; - -use crate::http::RawStr; - -/// A field name composed of keys. -/// -/// A form field name is composed of _keys_, delimited by `.` or `[]`. Keys, in -/// turn, are composed of _indices_, delimited by `:`. The graphic below -/// illustrates this composition for a single field in `$name=$value` format: -/// -/// ```text -/// food.bart[bar:foo].blam[0_0][1000]=some-value -/// name |--------------------------------| -/// key |--| |--| |-----| |--| |-| |--| -/// index |--| |--| |-| |-| |--| |-| |--| -/// ``` -/// -/// A `Name` is a wrapper around the field name string with methods to easily -/// access its sub-components. -/// -/// # Serialization -/// -/// A value of this type is serialized exactly as an `&str` consisting of the -/// entire field name. -#[repr(transparent)] -#[derive(RefCast)] -pub struct Name(str); - -impl Name { - /// Wraps a string as a `Name`. This is cost-free. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Name; - /// - /// let name = Name::new("a.b.c"); - /// assert_eq!(name.as_str(), "a.b.c"); - /// ``` - pub fn new + ?Sized>(string: &S) -> &Name { - Name::ref_cast(string.as_ref()) - } - - /// Returns an iterator over the keys of `self`, including empty keys. - /// - /// See the [top-level docs](Self) for a description of "keys". - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Name; - /// - /// let name = Name::new("apple.b[foo:bar]zoo.[barb].bat"); - /// let keys: Vec<_> = name.keys().map(|k| k.as_str()).collect(); - /// assert_eq!(keys, &["apple", "b", "foo:bar", "zoo", "", "barb", "bat"]); - /// ``` - pub fn keys(&self) -> impl Iterator { - struct Keys<'v>(NameView<'v>); - - impl<'v> Iterator for Keys<'v> { - type Item = &'v Key; - - fn next(&mut self) -> Option { - if self.0.exhausted() { - return None; - } - - let key = self.0.key_lossy(); - self.0.shift(); - Some(key) - } - } - - Keys(NameView::new(self)) - } - - /// Returns an iterator over overlapping name prefixes of `self`, each - /// succeeding prefix containing one more key than the previous. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Name; - /// - /// let name = Name::new("apple.b[foo:bar]"); - /// let prefixes: Vec<_> = name.prefixes().map(|p| p.as_str()).collect(); - /// assert_eq!(prefixes, &["apple", "apple.b", "apple.b[foo:bar]"]); - /// - /// let name = Name::new("a.b.[foo]"); - /// let prefixes: Vec<_> = name.prefixes().map(|p| p.as_str()).collect(); - /// assert_eq!(prefixes, &["a", "a.b", "a.b.", "a.b.[foo]"]); - /// ``` - pub fn prefixes(&self) -> impl Iterator { - struct Prefixes<'v>(NameView<'v>); - - impl<'v> Iterator for Prefixes<'v> { - type Item = &'v Name; - - fn next(&mut self) -> Option { - if self.0.exhausted() { - return None; - } - - let name = self.0.as_name(); - self.0.shift(); - Some(name) - } - } - - Prefixes(NameView::new(self)) - } - - /// Borrows the underlying string. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Name; - /// - /// let name = Name::new("a.b.c"); - /// assert_eq!(name.as_str(), "a.b.c"); - /// ``` - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl serde::Serialize for Name { - fn serialize(&self, ser: S) -> Result - where S: serde::Serializer - { - self.0.serialize(ser) - } -} - -impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Name { - fn deserialize(de: D) -> Result - where D: serde::Deserializer<'de> - { - <&'a str as serde::Deserialize<'de>>::deserialize(de).map(Name::new) - } -} - -impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a Name { - #[inline] - fn from(string: &'a S) -> Self { - Name::new(string) - } -} - -impl Deref for Name { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl> core::ops::Index for Name { - type Output = Name; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0[index].into() - } -} - -impl PartialEq for Name { - fn eq(&self, other: &Self) -> bool { - self.keys().eq(other.keys()) - } -} - -impl PartialEq for Name { - fn eq(&self, other: &str) -> bool { - self == Name::new(other) - } -} - -impl PartialEq for str { - fn eq(&self, other: &Name) -> bool { - Name::new(self) == other - } -} - -impl PartialEq<&str> for Name { - fn eq(&self, other: &&str) -> bool { - self == Name::new(other) - } -} - -impl PartialEq for &str { - fn eq(&self, other: &Name) -> bool { - Name::new(self) == other - } -} - -impl AsRef for str { - fn as_ref(&self) -> &Name { - Name::new(self) - } -} - -impl AsRef for RawStr { - fn as_ref(&self) -> &Name { - Name::new(self) - } -} - -impl Eq for Name { } - -impl std::hash::Hash for Name { - fn hash(&self, state: &mut H) { - self.keys().for_each(|k| k.0.hash(state)) - } -} - -impl std::fmt::Display for Name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::fmt::Debug for Name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -/// A field name key composed of indices. -/// -/// A form field name key is composed of _indices_, delimited by `:`. The -/// graphic below illustrates this composition for a single field in -/// `$name=$value` format: -/// -/// ```text -/// food.bart[bar:foo:baz]=some-value -/// name |--------------------| -/// key |--| |--| |---------| -/// index |--| |--| |-| |-| |-| -/// ``` -/// -/// A `Key` is a wrapper around a given key string with methods to easily access -/// its indices. -/// -/// # Serialization -/// -/// A value of this type is serialized exactly as an `&str` consisting of the -/// entire key. -#[repr(transparent)] -#[derive(RefCast, Debug, PartialEq, Eq, Hash)] -pub struct Key(str); - -impl Key { - /// Wraps a string as a `Key`. This is cost-free. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Key; - /// - /// let key = Key::new("a:b:c"); - /// assert_eq!(key.as_str(), "a:b:c"); - /// ``` - pub fn new + ?Sized>(string: &S) -> &Key { - Key::ref_cast(string.as_ref()) - } - - /// Returns an iterator over the indices of `self`, including empty indices. - /// - /// See the [top-level docs](Self) for a description of "indices". - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Key; - /// - /// let key = Key::new("foo:bar::baz:a.b.c"); - /// let indices: Vec<_> = key.indices().collect(); - /// assert_eq!(indices, &["foo", "bar", "", "baz", "a.b.c"]); - /// ``` - pub fn indices(&self) -> impl Iterator { - self.split(':') - } - - /// Borrows the underlying string. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::Key; - /// - /// let key = Key::new("a:b:c"); - /// assert_eq!(key.as_str(), "a:b:c"); - /// ``` - pub fn as_str(&self) -> &str { - &*self - } -} - -impl Deref for Key { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl serde::Serialize for Key { - fn serialize(&self, ser: S) -> Result - where S: serde::Serializer - { - self.0.serialize(ser) - } -} - -impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Key { - fn deserialize(de: D) -> Result - where D: serde::Deserializer<'de> - { - <&'a str as serde::Deserialize<'de>>::deserialize(de).map(Key::new) - } -} - -impl> core::ops::Index for Key { - type Output = Key; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0[index].into() - } -} - -impl PartialEq for Key { - fn eq(&self, other: &str) -> bool { - self == Key::new(other) - } -} - -impl PartialEq for str { - fn eq(&self, other: &Key) -> bool { - Key::new(self) == other - } -} - -impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a Key { - #[inline] - fn from(string: &'a S) -> Self { - Key::new(string) - } -} - -impl AsRef for str { - fn as_ref(&self) -> &Key { - Key::new(self) - } -} - -impl AsRef for RawStr { - fn as_ref(&self) -> &Key { - Key::new(self) - } -} - -impl std::fmt::Display for Key { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -/// A sliding-prefix view into a [`Name`]. -/// -/// A [`NameView`] maintains a sliding key view into a [`Name`]. The current key -/// ([`key()`]) can be [`shift()`ed](NameView::shift()) one key to the right. -/// The `Name` prefix including the current key can be extracted via -/// [`as_name()`] and the prefix _not_ including the current key via -/// [`parent()`]. -/// -/// [`key()`]: NameView::key() -/// [`as_name()`]: NameView::as_name() -/// [`parent()`]: NameView::parent() -/// -/// This is best illustrated via an example: -/// -/// ```rust -/// use rocket::form::name::NameView; -/// -/// // The view begins at the first key. Illustrated: `(a).b[c:d]` where -/// // parenthesis enclose the current key. -/// let mut view = NameView::new("a.b[c:d]"); -/// assert_eq!(view.key().unwrap(), "a"); -/// assert_eq!(view.as_name(), "a"); -/// assert_eq!(view.parent(), None); -/// -/// // Shifted once to the right views the second key: `a.(b)[c:d]`. -/// view.shift(); -/// assert_eq!(view.key().unwrap(), "b"); -/// assert_eq!(view.as_name(), "a.b"); -/// assert_eq!(view.parent().unwrap(), "a"); -/// -/// // Shifting again now has predictable results: `a.b[(c:d)]`. -/// view.shift(); -/// assert_eq!(view.key().unwrap(), "c:d"); -/// assert_eq!(view.as_name(), "a.b[c:d]"); -/// assert_eq!(view.parent().unwrap(), "a.b"); -/// -/// // Shifting past the end means we have no further keys. -/// view.shift(); -/// assert_eq!(view.key(), None); -/// assert_eq!(view.key_lossy(), ""); -/// assert_eq!(view.as_name(), "a.b[c:d]"); -/// assert_eq!(view.parent().unwrap(), "a.b[c:d]"); -/// -/// view.shift(); -/// assert_eq!(view.key(), None); -/// assert_eq!(view.as_name(), "a.b[c:d]"); -/// assert_eq!(view.parent().unwrap(), "a.b[c:d]"); -/// ``` -/// -/// # Equality -/// -/// `PartialEq`, `Eq`, and `Hash` all operate on the name prefix including the -/// current key. Only key values are compared; delimiters are insignificant. -/// Again, illustrated via examples: -/// -/// ```rust -/// use rocket::form::name::NameView; -/// -/// let mut view = NameView::new("a.b[c:d]"); -/// assert_eq!(view, "a"); -/// -/// // Shifted once to the right views the second key: `a.(b)[c:d]`. -/// view.shift(); -/// assert_eq!(view.key().unwrap(), "b"); -/// assert_eq!(view.as_name(), "a.b"); -/// assert_eq!(view, "a.b"); -/// assert_eq!(view, "a[b]"); -/// -/// // Shifting again now has predictable results: `a.b[(c:d)]`. -/// view.shift(); -/// assert_eq!(view, "a.b[c:d]"); -/// assert_eq!(view, "a.b.c:d"); -/// assert_eq!(view, "a[b].c:d"); -/// assert_eq!(view, "a[b]c:d"); -/// ``` -#[derive(Copy, Clone)] -pub struct NameView<'v> { - name: &'v Name, - start: usize, - end: usize, -} - -impl<'v> NameView<'v> { - /// Initializes a new `NameView` at the first key of `name`. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a.b[c:d]"); - /// assert_eq!(view.key().unwrap(), "a"); - /// assert_eq!(view.as_name(), "a"); - /// assert_eq!(view.parent(), None); - /// ``` - pub fn new>(name: N) -> Self { - let mut view = NameView { name: name.into(), start: 0, end: 0 }; - view.shift(); - view - } - - /// Shifts the current key once to the right. - /// - /// # Examples - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a.b[c:d][d.e]"); - /// assert_eq!(view.key().unwrap(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.key().unwrap(), "b"); - /// - /// view.shift(); - /// assert_eq!(view.key().unwrap(), "c:d"); - /// - /// view.shift(); - /// assert_eq!(view.key().unwrap(), "d.e"); - /// ``` - /// - /// Malformed strings can have interesting results: - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[c.d"); - /// assert_eq!(view.key_lossy(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), "c.d"); - /// - /// let mut view = NameView::new("a[c[.d]"); - /// assert_eq!(view.key_lossy(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), "c[.d"); - /// - /// view.shift(); - /// assert_eq!(view.key(), None); - /// - /// let mut view = NameView::new("foo[c[.d]]"); - /// assert_eq!(view.key_lossy(), "foo"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), "c[.d"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), "]"); - /// - /// view.shift(); - /// assert_eq!(view.key(), None); - /// ``` - pub fn shift(&mut self) { - const START_DELIMS: &'static [char] = &['.', '[']; - - let string = &self.name[self.end..]; - let bytes = string.as_bytes(); - let shift = match bytes.get(0) { - None | Some(b'=') => 0, - Some(b'[') => match memchr::memchr(b']', bytes) { - Some(j) => j + 1, - None => bytes.len(), - }, - Some(b'.') => match string[1..].find(START_DELIMS) { - Some(j) => j + 1, - None => bytes.len(), - }, - _ => match string.find(START_DELIMS) { - Some(j) => j, - None => bytes.len() - } - }; - - debug_assert!(self.end + shift <= self.name.len()); - *self = NameView { - name: self.name, - start: self.end, - end: self.end + shift, - }; - } - - /// Returns the key currently viewed by `self` if it is non-empty. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[b]"); - /// assert_eq!(view.key().unwrap(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.key().unwrap(), "b"); - /// - /// view.shift(); - /// assert_eq!(view.key(), None); - /// # view.shift(); assert_eq!(view.key(), None); - /// # view.shift(); assert_eq!(view.key(), None); - /// ``` - pub fn key(&self) -> Option<&'v Key> { - let lossy_key = self.key_lossy(); - if lossy_key.is_empty() { - return None; - } - - Some(lossy_key) - } - - /// Returns the key currently viewed by `self`, even if it is non-empty. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[b]"); - /// assert_eq!(view.key_lossy(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), "b"); - /// - /// view.shift(); - /// assert_eq!(view.key_lossy(), ""); - /// # view.shift(); assert_eq!(view.key_lossy(), ""); - /// # view.shift(); assert_eq!(view.key_lossy(), ""); - /// ``` - pub fn key_lossy(&self) -> &'v Key { - let view = &self.name[self.start..self.end]; - let key = match view.as_bytes().get(0) { - Some(b'.') => &view[1..], - Some(b'[') if view.ends_with(']') => &view[1..view.len() - 1], - Some(b'[') if self.is_at_last() => &view[1..], - _ => view - }; - - key.0.into() - } - - /// Returns the `Name` _up to and including_ the current key. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[b]"); - /// assert_eq!(view.as_name(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.as_name(), "a[b]"); - /// # view.shift(); assert_eq!(view.as_name(), "a[b]"); - /// # view.shift(); assert_eq!(view.as_name(), "a[b]"); - /// ``` - pub fn as_name(&self) -> &'v Name { - &self.name[..self.end] - } - - /// Returns the `Name` _prior to_ the current key. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[b]"); - /// assert_eq!(view.parent(), None); - /// - /// view.shift(); - /// assert_eq!(view.parent().unwrap(), "a"); - /// - /// view.shift(); - /// assert_eq!(view.parent().unwrap(), "a[b]"); - /// # view.shift(); assert_eq!(view.parent().unwrap(), "a[b]"); - /// # view.shift(); assert_eq!(view.parent().unwrap(), "a[b]"); - /// ``` - pub fn parent(&self) -> Option<&'v Name> { - if self.start > 0 { - Some(&self.name[..self.start]) - } else { - None - } - } - - /// Returns the underlying `Name`. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameView; - /// - /// let mut view = NameView::new("a[b]"); - /// assert_eq!(view.source(), "a[b]"); - /// - /// view.shift(); - /// assert_eq!(view.source(), "a[b]"); - /// - /// view.shift(); - /// assert_eq!(view.source(), "a[b]"); - /// - /// # view.shift(); assert_eq!(view.source(), "a[b]"); - /// # view.shift(); assert_eq!(view.source(), "a[b]"); - /// ``` - pub fn source(&self) -> &'v Name { - self.name - } - - // This is the last key. The next `shift()` will exhaust `self`. - fn is_at_last(&self) -> bool { - self.end == self.name.len() - } - - // There are no more keys. A `shift` will do nothing. - fn exhausted(&self) -> bool { - self.start == self.name.len() - } -} - -impl std::fmt::Debug for NameView<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_name().fmt(f) - } -} - -impl std::fmt::Display for NameView<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_name().fmt(f) - } -} - -impl<'a, 'b> PartialEq> for NameView<'a> { - fn eq(&self, other: &NameView<'b>) -> bool { - self.as_name() == other.as_name() - } -} - -impl> PartialEq for NameView<'_> { - fn eq(&self, other: &B) -> bool { - other == self.as_name() - } -} - -impl Eq for NameView<'_> { } - -impl std::hash::Hash for NameView<'_> { - fn hash(&self, state: &mut H) { - self.as_name().hash(state) - } -} - -impl std::borrow::Borrow for NameView<'_> { - fn borrow(&self) -> &Name { - self.as_name() - } -} - -/// A potentially owned [`Name`]. -/// -/// Constructible from a [`NameView`], [`Name`], `&str`, or `String`, a -/// `NameBuf` acts much like a [`Name`] but can be converted into an owned -/// version via [`IntoOwned`](crate::http::ext::IntoOwned). -/// -/// ```rust -/// use rocket::form::name::NameBuf; -/// use rocket::http::ext::IntoOwned; -/// -/// let alloc = String::from("a.b.c"); -/// let name = NameBuf::from(alloc.as_str()); -/// let owned: NameBuf<'static> = name.into_owned(); -/// ``` -#[derive(Clone)] -pub struct NameBuf<'v> { - left: &'v Name, - right: Cow<'v, str>, -} - -impl<'v> NameBuf<'v> { - #[inline] - fn split(&self) -> (&Name, &Name) { - (self.left, Name::new(&self.right)) - } - - /// Returns an iterator over the keys of `self`, including empty keys. - /// - /// See [`Name`] for a description of "keys". - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameBuf; - /// - /// let name = NameBuf::from("apple.b[foo:bar]zoo.[barb].bat"); - /// let keys: Vec<_> = name.keys().map(|k| k.as_str()).collect(); - /// assert_eq!(keys, &["apple", "b", "foo:bar", "zoo", "", "barb", "bat"]); - /// ``` - #[inline] - pub fn keys(&self) -> impl Iterator { - let (left, right) = self.split(); - left.keys().chain(right.keys()) - } - - /// Returns `true` if `self` is empty. - /// - /// # Example - /// - /// ```rust - /// use rocket::form::name::NameBuf; - /// - /// let name = NameBuf::from("apple.b[foo:bar]zoo.[barb].bat"); - /// assert!(!name.is_empty()); - /// - /// let name = NameBuf::from(""); - /// assert!(name.is_empty()); - /// ``` - #[inline] - pub fn is_empty(&self) -> bool { - let (left, right) = self.split(); - left.is_empty() && right.is_empty() - } -} - -impl crate::http::ext::IntoOwned for NameBuf<'_> { - type Owned = NameBuf<'static>; - - fn into_owned(self) -> Self::Owned { - let right = match (self.left, self.right) { - (l, Cow::Owned(r)) if l.is_empty() => Cow::Owned(r), - (l, r) if l.is_empty() => r.to_string().into(), - (l, r) if r.is_empty() => l.to_string().into(), - (l, r) => format!("{}.{}", l, r).into(), - }; - - NameBuf { left: "".into(), right } - } -} - -impl serde::Serialize for NameBuf<'_> { - fn serialize(&self, serializer: S) -> Result - where S: serde::Serializer - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'v> From> for NameBuf<'v> { - fn from(nv: NameView<'v>) -> Self { - NameBuf { left: nv.as_name(), right: Cow::Borrowed("") } - } -} - -impl<'v> From<&'v Name> for NameBuf<'v> { - fn from(name: &'v Name) -> Self { - NameBuf { left: name, right: Cow::Borrowed("") } - } -} - -impl<'v> From<&'v str> for NameBuf<'v> { - fn from(name: &'v str) -> Self { - NameBuf::from((None, Cow::Borrowed(name))) - } -} - -impl<'v> From for NameBuf<'v> { - fn from(name: String) -> Self { - NameBuf::from((None, Cow::Owned(name))) - } -} - -#[doc(hidden)] -impl<'v> From<(Option<&'v Name>, Cow<'v, str>)> for NameBuf<'v> { - fn from((prefix, right): (Option<&'v Name>, Cow<'v, str>)) -> Self { - match prefix { - Some(left) => NameBuf { left, right }, - None => NameBuf { left: "".into(), right } - } - } -} - -#[doc(hidden)] -impl<'v> From<(Option<&'v Name>, &'v str)> for NameBuf<'v> { - fn from((prefix, suffix): (Option<&'v Name>, &'v str)) -> Self { - NameBuf::from((prefix, Cow::Borrowed(suffix))) - } -} - -#[doc(hidden)] -impl<'v> From<(&'v Name, &'v str)> for NameBuf<'v> { - fn from((prefix, suffix): (&'v Name, &'v str)) -> Self { - NameBuf::from((Some(prefix), Cow::Borrowed(suffix))) - } -} - -impl std::fmt::Debug for NameBuf<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\"")?; - - let (left, right) = self.split(); - if !left.is_empty() { write!(f, "{}", left.escape_debug())? } - if !right.is_empty() { - if !left.is_empty() { f.write_str(".")?; } - write!(f, "{}", right.escape_debug())?; - } - - write!(f, "\"") - } -} - -impl std::fmt::Display for NameBuf<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (left, right) = self.split(); - if !left.is_empty() { left.fmt(f)?; } - if !right.is_empty() { - if !left.is_empty() { f.write_str(".")?; } - right.fmt(f)?; - } - - Ok(()) - } -} - -impl PartialEq for NameBuf<'_> { - fn eq(&self, other: &Self) -> bool { - self.keys().eq(other.keys()) - } -} - -impl + ?Sized> PartialEq for NameBuf<'_> { - fn eq(&self, other: &N) -> bool { - self.keys().eq(other.as_ref().keys()) - } -} - -impl PartialEq for NameBuf<'_> { - fn eq(&self, other: &Name) -> bool { - self.keys().eq(other.keys()) - } -} - -impl PartialEq> for Name { - fn eq(&self, other: &NameBuf<'_>) -> bool { - self.keys().eq(other.keys()) - } -} - -impl PartialEq> for str { - fn eq(&self, other: &NameBuf<'_>) -> bool { - Name::new(self) == other - } -} - -impl PartialEq> for &str { - fn eq(&self, other: &NameBuf<'_>) -> bool { - Name::new(self) == other - } -} - -impl Eq for NameBuf<'_> { } - -impl std::hash::Hash for NameBuf<'_> { - fn hash(&self, state: &mut H) { - self.keys().for_each(|k| k.0.hash(state)) - } -} - -impl indexmap::Equivalent for NameBuf<'_> { - fn equivalent(&self, key: &Name) -> bool { - self.keys().eq(key.keys()) - } -} - -impl indexmap::Equivalent> for Name { - fn equivalent(&self, key: &NameBuf<'_>) -> bool { - self.keys().eq(key.keys()) - } -} diff --git a/core/lib/src/form/name/buf.rs b/core/lib/src/form/name/buf.rs new file mode 100644 index 00000000..9cd15eaf --- /dev/null +++ b/core/lib/src/form/name/buf.rs @@ -0,0 +1,223 @@ +use std::borrow::Cow; + +use crate::form::name::*; + +/// A potentially owned [`Name`]. +/// +/// Constructible from a [`NameView`], [`Name`], `&str`, or `String`, a +/// `NameBuf` acts much like a [`Name`] but can be converted into an owned +/// version via [`IntoOwned`](crate::http::ext::IntoOwned). +/// +/// ```rust +/// use rocket::form::name::NameBuf; +/// use rocket::http::ext::IntoOwned; +/// +/// let alloc = String::from("a.b.c"); +/// let name = NameBuf::from(alloc.as_str()); +/// let owned: NameBuf<'static> = name.into_owned(); +/// ``` +#[derive(Clone)] +pub struct NameBuf<'v> { + left: &'v Name, + right: Cow<'v, str>, +} + +impl<'v> NameBuf<'v> { + #[inline] + fn split(&self) -> (&Name, &Name) { + (self.left, Name::new(&self.right)) + } + + /// Returns an iterator over the keys of `self`, including empty keys. + /// + /// See [`Name`] for a description of "keys". + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameBuf; + /// + /// let name = NameBuf::from("apple.b[foo:bar]zoo.[barb].bat"); + /// let keys: Vec<_> = name.keys().map(|k| k.as_str()).collect(); + /// assert_eq!(keys, &["apple", "b", "foo:bar", "zoo", "", "barb", "bat"]); + /// ``` + #[inline] + pub fn keys(&self) -> impl Iterator { + let (left, right) = self.split(); + left.keys().chain(right.keys()) + } + + /// Returns `true` if `self` is empty. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameBuf; + /// + /// let name = NameBuf::from("apple.b[foo:bar]zoo.[barb].bat"); + /// assert!(!name.is_empty()); + /// + /// let name = NameBuf::from(""); + /// assert!(name.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + let (left, right) = self.split(); + left.is_empty() && right.is_empty() + } +} + +impl crate::http::ext::IntoOwned for NameBuf<'_> { + type Owned = NameBuf<'static>; + + fn into_owned(self) -> Self::Owned { + let right = match (self.left, self.right) { + (l, Cow::Owned(r)) if l.is_empty() => Cow::Owned(r), + (l, r) if l.is_empty() => r.to_string().into(), + (l, r) if r.is_empty() => l.to_string().into(), + (l, r) => format!("{}.{}", l, r).into(), + }; + + NameBuf { left: "".into(), right } + } +} + +impl serde::Serialize for NameBuf<'_> { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'v> From> for NameBuf<'v> { + fn from(nv: NameView<'v>) -> Self { + NameBuf { left: nv.as_name(), right: Cow::Borrowed("") } + } +} + +impl<'v> From<&'v Name> for NameBuf<'v> { + fn from(name: &'v Name) -> Self { + NameBuf { left: name, right: Cow::Borrowed("") } + } +} + +impl<'v> From<&'v str> for NameBuf<'v> { + fn from(name: &'v str) -> Self { + NameBuf::from((None, Cow::Borrowed(name))) + } +} + +impl<'v> From for NameBuf<'v> { + fn from(name: String) -> Self { + NameBuf::from((None, Cow::Owned(name))) + } +} + +#[doc(hidden)] +impl<'v> From<(Option<&'v Name>, Cow<'v, str>)> for NameBuf<'v> { + fn from((prefix, right): (Option<&'v Name>, Cow<'v, str>)) -> Self { + match prefix { + Some(left) => NameBuf { left, right }, + None => NameBuf { left: "".into(), right } + } + } +} + +#[doc(hidden)] +impl<'v> From<(Option<&'v Name>, &'v str)> for NameBuf<'v> { + fn from((prefix, suffix): (Option<&'v Name>, &'v str)) -> Self { + NameBuf::from((prefix, Cow::Borrowed(suffix))) + } +} + +#[doc(hidden)] +impl<'v> From<(&'v Name, &'v str)> for NameBuf<'v> { + fn from((prefix, suffix): (&'v Name, &'v str)) -> Self { + NameBuf::from((Some(prefix), Cow::Borrowed(suffix))) + } +} + +impl std::fmt::Debug for NameBuf<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\"")?; + + let (left, right) = self.split(); + if !left.is_empty() { write!(f, "{}", left.escape_debug())? } + if !right.is_empty() { + if !left.is_empty() { f.write_str(".")?; } + write!(f, "{}", right.escape_debug())?; + } + + write!(f, "\"") + } +} + +impl std::fmt::Display for NameBuf<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (left, right) = self.split(); + if !left.is_empty() { left.fmt(f)?; } + if !right.is_empty() { + if !left.is_empty() { f.write_str(".")?; } + right.fmt(f)?; + } + + Ok(()) + } +} + +impl PartialEq for NameBuf<'_> { + fn eq(&self, other: &Self) -> bool { + self.keys().eq(other.keys()) + } +} + +impl + ?Sized> PartialEq for NameBuf<'_> { + fn eq(&self, other: &N) -> bool { + self.keys().eq(other.as_ref().keys()) + } +} + +impl PartialEq for NameBuf<'_> { + fn eq(&self, other: &Name) -> bool { + self.keys().eq(other.keys()) + } +} + +impl PartialEq> for Name { + fn eq(&self, other: &NameBuf<'_>) -> bool { + self.keys().eq(other.keys()) + } +} + +impl PartialEq> for str { + fn eq(&self, other: &NameBuf<'_>) -> bool { + Name::new(self) == other + } +} + +impl PartialEq> for &str { + fn eq(&self, other: &NameBuf<'_>) -> bool { + Name::new(self) == other + } +} + +impl Eq for NameBuf<'_> { } + +impl std::hash::Hash for NameBuf<'_> { + fn hash(&self, state: &mut H) { + self.keys().for_each(|k| k.hash(state)) + } +} + +impl indexmap::Equivalent for NameBuf<'_> { + fn equivalent(&self, key: &Name) -> bool { + self.keys().eq(key.keys()) + } +} + +impl indexmap::Equivalent> for Name { + fn equivalent(&self, key: &NameBuf<'_>) -> bool { + self.keys().eq(key.keys()) + } +} diff --git a/core/lib/src/form/name/file_name.rs b/core/lib/src/form/name/file_name.rs new file mode 100644 index 00000000..185b4de9 --- /dev/null +++ b/core/lib/src/form/name/file_name.rs @@ -0,0 +1,215 @@ +use ref_cast::RefCast; + +use crate::http::RawStr; + +/// A file name in a file attachment or multipart [`DataField`]. +/// +/// A `Content-Disposition` header, either in a response or a multipart field, +/// can optionally specify a `filename` directive as identifying information for +/// the attached file. This type represents the value of that directive. +/// +/// # Safety +/// +/// There are no restrictions on the value of the directive. In particular, the +/// value can be wholly unsafe to use as a file name in common contexts. As +/// such, Rocket sanitizes the value into a version that _is_ safe to use as a +/// file name in common contexts; this sanitized version can be retrieved via +/// [`FileName::as_str()`] and is returned by [`TempFile::name()`]. +/// +/// You will likely want to prepend or append random or user-specific components +/// to the name to avoid collisions; UUIDs make for a good "random" data. You +/// may also prefer to avoid the value in the directive entirely by using a +/// safe, application-generated name instead. +/// +/// [`TempFile::name()`]: crate::data::TempFile::name +/// [`DataField`]: crate::form::DataField +#[repr(transparent)] +#[derive(RefCast, Debug)] +pub struct FileName(str); + +impl FileName { + /// Wraps a string as a `FileName`. This is cost-free. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::FileName; + /// + /// let name = FileName::new("some-file.txt"); + /// assert_eq!(name.as_str(), Some("some-file")); + /// + /// let name = FileName::new("some-file.txt"); + /// assert_eq!(name.dangerous_unsafe_unsanitized_raw(), "some-file.txt"); + /// ``` + pub fn new + ?Sized>(string: &S) -> &FileName { + FileName::ref_cast(string.as_ref()) + } + + /// The sanitized file name, stripped of any file extension and special + /// characters, safe for use as a file name. + /// + /// # Sanitization + /// + /// A "sanitized" file name is a non-empty string, stripped of its file + /// extension, which is not a platform-specific reserved name and does not + /// contain any platform-specific special characters. + /// + /// On Unix, these are the characters `'.', '/', '\\', '<', '>', '|', ':', + /// '(', ')', '&', ';', '#', '?', '*'`. + /// + /// On Windows (and non-Unix OSs), these are the characters `'.', '<', '>', + /// ':', '"', '/', '\\', '|', '?', '*', ',', ';', '=', '(', ')', '&', '#'`, + /// and the reserved names `"CON", "PRN", "AUX", "NUL", "COM1", "COM2", + /// "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", + /// "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"`. + /// + /// Additionally, all control characters are considered "special". + /// + /// An attempt is made to transform the raw file name into a sanitized + /// version by identifying a valid substring of the raw file name that meets + /// this criteria. If none is found, `None` is returned. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::FileName; + /// + /// let name = FileName::new("some-file.txt"); + /// assert_eq!(name.as_str(), Some("some-file")); + /// + /// let name = FileName::new("some-file.txt.zip"); + /// assert_eq!(name.as_str(), Some("some-file")); + /// + /// let name = FileName::new("../../../../etc/shadow"); + /// assert_eq!(name.as_str(), Some("shadow")); + /// + /// let name = FileName::new("/etc/.shadow"); + /// assert_eq!(name.as_str(), Some("shadow")); + /// + /// let name = FileName::new("/a/b/some/file.txt.zip"); + /// assert_eq!(name.as_str(), Some("file")); + /// + /// let name = FileName::new("/a/b/some/.file.txt.zip"); + /// assert_eq!(name.as_str(), Some("file")); + /// + /// let name = FileName::new("/a/b/some/.*file.txt.zip"); + /// assert_eq!(name.as_str(), Some("file")); + /// + /// let name = FileName::new("a/\\b/some/.*file<.txt.zip"); + /// assert_eq!(name.as_str(), Some("file")); + /// + /// let name = FileName::new(">>>.foo.txt"); + /// assert_eq!(name.as_str(), Some("foo")); + /// + /// let name = FileName::new("b:c"); + /// assert_eq!(name.as_str(), Some("b")); + /// + /// let name = FileName::new("//./.<>"); + /// assert_eq!(name.as_str(), None); + /// ``` + pub fn as_str(&self) -> Option<&str> { + #[cfg(not(unix))] + let (bad_char, bad_name) = { + static BAD_CHARS: &[char] = &[ + // Microsoft says these are invalid. + '.', '<', '>', ':', '"', '/', '\\', '|', '?', '*', + + // `cmd.exe` treats these specially. + ',', ';', '=', + + // These are treated specially by unix-like shells. + '(', ')', '&', '#', + ]; + + // Microsoft says these are reserved. + static BAD_NAMES: &[str] = &[ + "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", + "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + ]; + + let bad_char = |c| BAD_CHARS.contains(&c) || c.is_control(); + let bad_name = |n| BAD_NAMES.contains(n); + (bad_char, bad_name) + }; + + #[cfg(unix)] + let (bad_char, bad_name) = { + static BAD_CHARS: &[char] = &[ + // These have special meaning in a file name. + '.', '/', '\\', + + // These are treated specially by shells. + '<', '>', '|', ':', '(', ')', '&', ';', '#', '?', '*', + ]; + + let bad_char = |c| BAD_CHARS.contains(&c) || c.is_control(); + let bad_name = |_| false; + (bad_char, bad_name) + }; + + // Get the file name as a `str` without any extension(s). + let file_name = std::path::Path::new(&self.0) + .file_name() + .and_then(|n| n.to_str()) + .and_then(|n| n.split(bad_char).filter(|s| !s.is_empty()).next())?; + + // At this point, `file_name` can't contain `bad_chars` because of + // `.split()`, but it can be empty or reserved. + if file_name.is_empty() || bad_name(file_name) { + return None; + } + + Some(file_name) + } + + /// The raw, unsanitized, potentially unsafe file name. Prefer to use + /// [`FileName::as_str()`], always. + /// + /// # Danger + /// + /// This method returns the file name exactly as it was specified by the + /// client. You should **_not_** use this name _unless_ you require the + /// originally specified `filename` _and_ it is known to contain special, + /// potentially dangerous characters, _and_: + /// + /// 1. All clients are known to be trusted, perhaps because the server + /// only runs locally, serving known, local requests, or... + /// + /// 2. You will not use the file name to store a file on disk or any + /// context that expects a file name _and_ you will not use the + /// extension to determine how to handle/parse the data, or... + /// + /// 3. You will expertly process the raw name into a sanitized version for + /// use in specific contexts. + /// + /// If not all of these cases apply, use [`FileName::as_str()`]. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::FileName; + /// + /// let name = FileName::new("some-file.txt"); + /// assert_eq!(name.dangerous_unsafe_unsanitized_raw(), "some-file.txt"); + /// + /// let name = FileName::new("../../../../etc/shadow"); + /// assert_eq!(name.dangerous_unsafe_unsanitized_raw(), "../../../../etc/shadow"); + /// + /// let name = FileName::new("../../.ssh/id_rsa"); + /// assert_eq!(name.dangerous_unsafe_unsanitized_raw(), "../../.ssh/id_rsa"); + /// + /// let name = FileName::new("/Rocket.toml"); + /// assert_eq!(name.dangerous_unsafe_unsanitized_raw(), "/Rocket.toml"); + /// ``` + pub fn dangerous_unsafe_unsanitized_raw(&self) -> &RawStr { + self.0.into() + } +} + +impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a FileName { + #[inline] + fn from(string: &'a S) -> Self { + FileName::new(string) + } +} diff --git a/core/lib/src/form/name/key.rs b/core/lib/src/form/name/key.rs new file mode 100644 index 00000000..15db6cb2 --- /dev/null +++ b/core/lib/src/form/name/key.rs @@ -0,0 +1,147 @@ +use std::ops::Deref; + +use ref_cast::RefCast; + +use crate::http::RawStr; + +/// A field name key composed of indices. +/// +/// A form field name key is composed of _indices_, delimited by `:`. The +/// graphic below illustrates this composition for a single field in +/// `$name=$value` format: +/// +/// ```text +/// food.bart[bar:foo:baz]=some-value +/// name |--------------------| +/// key |--| |--| |---------| +/// index |--| |--| |-| |-| |-| +/// ``` +/// +/// A `Key` is a wrapper around a given key string with methods to easily access +/// its indices. +/// +/// # Serialization +/// +/// A value of this type is serialized exactly as an `&str` consisting of the +/// entire key. +#[repr(transparent)] +#[derive(RefCast, Debug, PartialEq, Eq, Hash)] +pub struct Key(str); + +impl Key { + /// Wraps a string as a `Key`. This is cost-free. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Key; + /// + /// let key = Key::new("a:b:c"); + /// assert_eq!(key.as_str(), "a:b:c"); + /// ``` + pub fn new + ?Sized>(string: &S) -> &Key { + Key::ref_cast(string.as_ref()) + } + + /// Returns an iterator over the indices of `self`, including empty indices. + /// + /// See the [top-level docs](Self) for a description of "indices". + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Key; + /// + /// let key = Key::new("foo:bar::baz:a.b.c"); + /// let indices: Vec<_> = key.indices().collect(); + /// assert_eq!(indices, &["foo", "bar", "", "baz", "a.b.c"]); + /// ``` + pub fn indices(&self) -> impl Iterator { + self.split(':') + } + + /// Borrows the underlying string. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Key; + /// + /// let key = Key::new("a:b:c"); + /// assert_eq!(key.as_str(), "a:b:c"); + /// ``` + pub fn as_str(&self) -> &str { + &*self + } +} + +impl Deref for Key { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl serde::Serialize for Key { + fn serialize(&self, ser: S) -> Result + where S: serde::Serializer + { + self.0.serialize(ser) + } +} + +impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Key { + fn deserialize(de: D) -> Result + where D: serde::Deserializer<'de> + { + <&'a str as serde::Deserialize<'de>>::deserialize(de).map(Key::new) + } +} + +impl> core::ops::Index for Key { + type Output = Key; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0[index].into() + } +} + +impl PartialEq for Key { + fn eq(&self, other: &str) -> bool { + self == Key::new(other) + } +} + +impl PartialEq for str { + fn eq(&self, other: &Key) -> bool { + Key::new(self) == other + } +} + +impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a Key { + #[inline] + fn from(string: &'a S) -> Self { + Key::new(string) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Key { + Key::new(self) + } +} + +impl AsRef for RawStr { + fn as_ref(&self) -> &Key { + Key::new(self) + } +} + +impl std::fmt::Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + diff --git a/core/lib/src/form/name/mod.rs b/core/lib/src/form/name/mod.rs new file mode 100644 index 00000000..bfbd90ea --- /dev/null +++ b/core/lib/src/form/name/mod.rs @@ -0,0 +1,13 @@ +//! Types for field names, name keys, and key indices. + +mod name; +mod view; +mod key; +mod buf; +mod file_name; + +pub use name::Name; +pub use view::NameView; +pub use key::Key; +pub use buf::NameBuf; +pub use file_name::FileName; diff --git a/core/lib/src/form/name/name.rs b/core/lib/src/form/name/name.rs new file mode 100644 index 00000000..c9caa4ee --- /dev/null +++ b/core/lib/src/form/name/name.rs @@ -0,0 +1,232 @@ +use std::ops::Deref; + +use ref_cast::RefCast; + +use crate::http::RawStr; +use crate::form::name::*; + +/// A field name composed of keys. +/// +/// A form field name is composed of _keys_, delimited by `.` or `[]`. Keys, in +/// turn, are composed of _indices_, delimited by `:`. The graphic below +/// illustrates this composition for a single field in `$name=$value` format: +/// +/// ```text +/// food.bart[bar:foo].blam[0_0][1000]=some-value +/// name |--------------------------------| +/// key |--| |--| |-----| |--| |-| |--| +/// index |--| |--| |-| |-| |--| |-| |--| +/// ``` +/// +/// A `Name` is a wrapper around the field name string with methods to easily +/// access its sub-components. +/// +/// # Serialization +/// +/// A value of this type is serialized exactly as an `&str` consisting of the +/// entire field name. +#[repr(transparent)] +#[derive(RefCast)] +pub struct Name(str); + +impl Name { + /// Wraps a string as a `Name`. This is cost-free. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Name; + /// + /// let name = Name::new("a.b.c"); + /// assert_eq!(name.as_str(), "a.b.c"); + /// ``` + pub fn new + ?Sized>(string: &S) -> &Name { + Name::ref_cast(string.as_ref()) + } + + /// Returns an iterator over the keys of `self`, including empty keys. + /// + /// See the [top-level docs](Self) for a description of "keys". + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Name; + /// + /// let name = Name::new("apple.b[foo:bar]zoo.[barb].bat"); + /// let keys: Vec<_> = name.keys().map(|k| k.as_str()).collect(); + /// assert_eq!(keys, &["apple", "b", "foo:bar", "zoo", "", "barb", "bat"]); + /// ``` + pub fn keys(&self) -> impl Iterator { + struct Keys<'v>(NameView<'v>); + + impl<'v> Iterator for Keys<'v> { + type Item = &'v Key; + + fn next(&mut self) -> Option { + if self.0.exhausted() { + return None; + } + + let key = self.0.key_lossy(); + self.0.shift(); + Some(key) + } + } + + Keys(NameView::new(self)) + } + + /// Returns an iterator over overlapping name prefixes of `self`, each + /// succeeding prefix containing one more key than the previous. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Name; + /// + /// let name = Name::new("apple.b[foo:bar]"); + /// let prefixes: Vec<_> = name.prefixes().map(|p| p.as_str()).collect(); + /// assert_eq!(prefixes, &["apple", "apple.b", "apple.b[foo:bar]"]); + /// + /// let name = Name::new("a.b.[foo]"); + /// let prefixes: Vec<_> = name.prefixes().map(|p| p.as_str()).collect(); + /// assert_eq!(prefixes, &["a", "a.b", "a.b.", "a.b.[foo]"]); + /// ``` + pub fn prefixes(&self) -> impl Iterator { + struct Prefixes<'v>(NameView<'v>); + + impl<'v> Iterator for Prefixes<'v> { + type Item = &'v Name; + + fn next(&mut self) -> Option { + if self.0.exhausted() { + return None; + } + + let name = self.0.as_name(); + self.0.shift(); + Some(name) + } + } + + Prefixes(NameView::new(self)) + } + + /// Borrows the underlying string. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::Name; + /// + /// let name = Name::new("a.b.c"); + /// assert_eq!(name.as_str(), "a.b.c"); + /// ``` + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl serde::Serialize for Name { + fn serialize(&self, ser: S) -> Result + where S: serde::Serializer + { + self.0.serialize(ser) + } +} + +impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Name { + fn deserialize(de: D) -> Result + where D: serde::Deserializer<'de> + { + <&'a str as serde::Deserialize<'de>>::deserialize(de).map(Name::new) + } +} + +impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a Name { + #[inline] + fn from(string: &'a S) -> Self { + Name::new(string) + } +} + +impl Deref for Name { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> core::ops::Index for Name { + type Output = Name; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0[index].into() + } +} + +impl PartialEq for Name { + fn eq(&self, other: &Self) -> bool { + self.keys().eq(other.keys()) + } +} + +impl PartialEq for Name { + fn eq(&self, other: &str) -> bool { + self == Name::new(other) + } +} + +impl PartialEq for str { + fn eq(&self, other: &Name) -> bool { + Name::new(self) == other + } +} + +impl PartialEq<&str> for Name { + fn eq(&self, other: &&str) -> bool { + self == Name::new(other) + } +} + +impl PartialEq for &str { + fn eq(&self, other: &Name) -> bool { + Name::new(self) == other + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Name { + Name::new(self) + } +} + +impl AsRef for RawStr { + fn as_ref(&self) -> &Name { + Name::new(self) + } +} + +impl Eq for Name { } + +impl std::hash::Hash for Name { + fn hash(&self, state: &mut H) { + self.keys().for_each(|k| k.hash(state)) + } +} + +impl std::fmt::Display for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Debug for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + diff --git a/core/lib/src/form/name/view.rs b/core/lib/src/form/name/view.rs new file mode 100644 index 00000000..7ddd55e5 --- /dev/null +++ b/core/lib/src/form/name/view.rs @@ -0,0 +1,357 @@ +use crate::form::name::*; + +/// A sliding-prefix view into a [`Name`]. +/// +/// A [`NameView`] maintains a sliding key view into a [`Name`]. The current key +/// ([`key()`]) can be [`shift()`ed](NameView::shift()) one key to the right. +/// The `Name` prefix including the current key can be extracted via +/// [`as_name()`] and the prefix _not_ including the current key via +/// [`parent()`]. +/// +/// [`key()`]: NameView::key() +/// [`as_name()`]: NameView::as_name() +/// [`parent()`]: NameView::parent() +/// +/// This is best illustrated via an example: +/// +/// ```rust +/// use rocket::form::name::NameView; +/// +/// // The view begins at the first key. Illustrated: `(a).b[c:d]` where +/// // parenthesis enclose the current key. +/// let mut view = NameView::new("a.b[c:d]"); +/// assert_eq!(view.key().unwrap(), "a"); +/// assert_eq!(view.as_name(), "a"); +/// assert_eq!(view.parent(), None); +/// +/// // Shifted once to the right views the second key: `a.(b)[c:d]`. +/// view.shift(); +/// assert_eq!(view.key().unwrap(), "b"); +/// assert_eq!(view.as_name(), "a.b"); +/// assert_eq!(view.parent().unwrap(), "a"); +/// +/// // Shifting again now has predictable results: `a.b[(c:d)]`. +/// view.shift(); +/// assert_eq!(view.key().unwrap(), "c:d"); +/// assert_eq!(view.as_name(), "a.b[c:d]"); +/// assert_eq!(view.parent().unwrap(), "a.b"); +/// +/// // Shifting past the end means we have no further keys. +/// view.shift(); +/// assert_eq!(view.key(), None); +/// assert_eq!(view.key_lossy(), ""); +/// assert_eq!(view.as_name(), "a.b[c:d]"); +/// assert_eq!(view.parent().unwrap(), "a.b[c:d]"); +/// +/// view.shift(); +/// assert_eq!(view.key(), None); +/// assert_eq!(view.as_name(), "a.b[c:d]"); +/// assert_eq!(view.parent().unwrap(), "a.b[c:d]"); +/// ``` +/// +/// # Equality +/// +/// `PartialEq`, `Eq`, and `Hash` all operate on the name prefix including the +/// current key. Only key values are compared; delimiters are insignificant. +/// Again, illustrated via examples: +/// +/// ```rust +/// use rocket::form::name::NameView; +/// +/// let mut view = NameView::new("a.b[c:d]"); +/// assert_eq!(view, "a"); +/// +/// // Shifted once to the right views the second key: `a.(b)[c:d]`. +/// view.shift(); +/// assert_eq!(view.key().unwrap(), "b"); +/// assert_eq!(view.as_name(), "a.b"); +/// assert_eq!(view, "a.b"); +/// assert_eq!(view, "a[b]"); +/// +/// // Shifting again now has predictable results: `a.b[(c:d)]`. +/// view.shift(); +/// assert_eq!(view, "a.b[c:d]"); +/// assert_eq!(view, "a.b.c:d"); +/// assert_eq!(view, "a[b].c:d"); +/// assert_eq!(view, "a[b]c:d"); +/// ``` +#[derive(Copy, Clone)] +pub struct NameView<'v> { + name: &'v Name, + start: usize, + end: usize, +} + +impl<'v> NameView<'v> { + /// Initializes a new `NameView` at the first key of `name`. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a.b[c:d]"); + /// assert_eq!(view.key().unwrap(), "a"); + /// assert_eq!(view.as_name(), "a"); + /// assert_eq!(view.parent(), None); + /// ``` + pub fn new>(name: N) -> Self { + let mut view = NameView { name: name.into(), start: 0, end: 0 }; + view.shift(); + view + } + + /// Shifts the current key once to the right. + /// + /// # Examples + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a.b[c:d][d.e]"); + /// assert_eq!(view.key().unwrap(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.key().unwrap(), "b"); + /// + /// view.shift(); + /// assert_eq!(view.key().unwrap(), "c:d"); + /// + /// view.shift(); + /// assert_eq!(view.key().unwrap(), "d.e"); + /// ``` + /// + /// Malformed strings can have interesting results: + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[c.d"); + /// assert_eq!(view.key_lossy(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), "c.d"); + /// + /// let mut view = NameView::new("a[c[.d]"); + /// assert_eq!(view.key_lossy(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), "c[.d"); + /// + /// view.shift(); + /// assert_eq!(view.key(), None); + /// + /// let mut view = NameView::new("foo[c[.d]]"); + /// assert_eq!(view.key_lossy(), "foo"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), "c[.d"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), "]"); + /// + /// view.shift(); + /// assert_eq!(view.key(), None); + /// ``` + pub fn shift(&mut self) { + const START_DELIMS: &'static [char] = &['.', '[']; + + let string = &self.name[self.end..]; + let bytes = string.as_bytes(); + let shift = match bytes.get(0) { + None | Some(b'=') => 0, + Some(b'[') => match memchr::memchr(b']', bytes) { + Some(j) => j + 1, + None => bytes.len(), + }, + Some(b'.') => match string[1..].find(START_DELIMS) { + Some(j) => j + 1, + None => bytes.len(), + }, + _ => match string.find(START_DELIMS) { + Some(j) => j, + None => bytes.len() + } + }; + + debug_assert!(self.end + shift <= self.name.len()); + *self = NameView { + name: self.name, + start: self.end, + end: self.end + shift, + }; + } + + /// Returns the key currently viewed by `self` if it is non-empty. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[b]"); + /// assert_eq!(view.key().unwrap(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.key().unwrap(), "b"); + /// + /// view.shift(); + /// assert_eq!(view.key(), None); + /// # view.shift(); assert_eq!(view.key(), None); + /// # view.shift(); assert_eq!(view.key(), None); + /// ``` + pub fn key(&self) -> Option<&'v Key> { + let lossy_key = self.key_lossy(); + if lossy_key.is_empty() { + return None; + } + + Some(lossy_key) + } + + /// Returns the key currently viewed by `self`, even if it is non-empty. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[b]"); + /// assert_eq!(view.key_lossy(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), "b"); + /// + /// view.shift(); + /// assert_eq!(view.key_lossy(), ""); + /// # view.shift(); assert_eq!(view.key_lossy(), ""); + /// # view.shift(); assert_eq!(view.key_lossy(), ""); + /// ``` + pub fn key_lossy(&self) -> &'v Key { + let view = &self.name[self.start..self.end]; + let key = match view.as_bytes().get(0) { + Some(b'.') => &view[1..], + Some(b'[') if view.ends_with(']') => &view[1..view.len() - 1], + Some(b'[') if self.is_at_last() => &view[1..], + _ => view + }; + + key.as_str().into() + } + + /// Returns the `Name` _up to and including_ the current key. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[b]"); + /// assert_eq!(view.as_name(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.as_name(), "a[b]"); + /// # view.shift(); assert_eq!(view.as_name(), "a[b]"); + /// # view.shift(); assert_eq!(view.as_name(), "a[b]"); + /// ``` + pub fn as_name(&self) -> &'v Name { + &self.name[..self.end] + } + + /// Returns the `Name` _prior to_ the current key. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[b]"); + /// assert_eq!(view.parent(), None); + /// + /// view.shift(); + /// assert_eq!(view.parent().unwrap(), "a"); + /// + /// view.shift(); + /// assert_eq!(view.parent().unwrap(), "a[b]"); + /// # view.shift(); assert_eq!(view.parent().unwrap(), "a[b]"); + /// # view.shift(); assert_eq!(view.parent().unwrap(), "a[b]"); + /// ``` + pub fn parent(&self) -> Option<&'v Name> { + if self.start > 0 { + Some(&self.name[..self.start]) + } else { + None + } + } + + /// Returns the underlying `Name`. + /// + /// # Example + /// + /// ```rust + /// use rocket::form::name::NameView; + /// + /// let mut view = NameView::new("a[b]"); + /// assert_eq!(view.source(), "a[b]"); + /// + /// view.shift(); + /// assert_eq!(view.source(), "a[b]"); + /// + /// view.shift(); + /// assert_eq!(view.source(), "a[b]"); + /// + /// # view.shift(); assert_eq!(view.source(), "a[b]"); + /// # view.shift(); assert_eq!(view.source(), "a[b]"); + /// ``` + pub fn source(&self) -> &'v Name { + self.name + } + + // This is the last key. The next `shift()` will exhaust `self`. + fn is_at_last(&self) -> bool { + self.end == self.name.len() + } + + // There are no more keys. A `shift` will do nothing. + pub(crate) fn exhausted(&self) -> bool { + self.start == self.name.len() + } +} + +impl std::fmt::Debug for NameView<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_name().fmt(f) + } +} + +impl std::fmt::Display for NameView<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_name().fmt(f) + } +} + +impl<'a, 'b> PartialEq> for NameView<'a> { + fn eq(&self, other: &NameView<'b>) -> bool { + self.as_name() == other.as_name() + } +} + +impl> PartialEq for NameView<'_> { + fn eq(&self, other: &B) -> bool { + other == self.as_name() + } +} + +impl Eq for NameView<'_> { } + +impl std::hash::Hash for NameView<'_> { + fn hash(&self, state: &mut H) { + self.as_name().hash(state) + } +} + +impl std::borrow::Borrow for NameView<'_> { + fn borrow(&self) -> &Name { + self.as_name() + } +} diff --git a/core/lib/src/form/parser.rs b/core/lib/src/form/parser.rs index 0f7b0948..3b58dd1c 100644 --- a/core/lib/src/form/parser.rs +++ b/core/lib/src/form/parser.rs @@ -182,7 +182,7 @@ impl<'r, 'i> MultipartParser<'r, 'i> { content_type, request: self.request, name: NameView::new(name), - file_name: file_name.and_then(|name| Some(FileName::new(name))), + file_name: file_name.map(FileName::new), data: Data::from(field), }) } else { diff --git a/scripts/test.sh b/scripts/test.sh index 2808832b..f9f2f27c 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -126,7 +126,6 @@ function run_benchmarks() { popd > /dev/null 2>&1 } - if [[ $1 == +* ]]; then CARGO="$CARGO $1" shift