Make 'FileName' a DST. Improve sanitization.

Resolves #1594.
This commit is contained in:
Sergio Benitez 2021-04-03 17:06:23 -07:00
parent fa1b75ba74
commit 579508d58f
13 changed files with 1225 additions and 1028 deletions

View File

@ -579,7 +579,7 @@ fn test_multipart() {
#[rocket::post("/", data = "<form>")] #[rocket::post("/", data = "<form>")]
fn form(form: Form<MyForm>) { fn form(form: Form<MyForm>) {
assert_eq!(form.names, &["abcd", "123"]); 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(); let client = Client::debug_with(rocket::routes![form]).unwrap();

View File

@ -22,7 +22,6 @@ all-features = true
default = [] default = []
tls = ["rocket_http/tls"] tls = ["rocket_http/tls"]
secrets = ["rocket_http/private-cookies"] secrets = ["rocket_http/private-cookies"]
trusted_client = []
[dependencies] [dependencies]
futures = "0.3.0" futures = "0.3.0"

View File

@ -3,7 +3,7 @@ use std::path::{PathBuf, Path};
use crate::http::{ContentType, Status}; use crate::http::{ContentType, Status};
use crate::data::{FromData, Data, Capped, N, Limits}; 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::outcome::IntoOutcome;
use crate::request::Request; use crate::request::Request;
@ -101,7 +101,7 @@ use either::Either;
pub enum TempFile<'v> { pub enum TempFile<'v> {
#[doc(hidden)] #[doc(hidden)]
File { File {
file_name: Option<&'v str>, file_name: Option<&'v FileName>,
content_type: Option<ContentType>, content_type: Option<ContentType>,
path: Either<TempPath, PathBuf>, path: Either<TempPath, PathBuf>,
len: u64, 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 multipart data form field can optionally specify the name of a file. A
/// A browser will typically send the actual name of a user's selected file /// browser will typically send the actual name of a user's selected file in
/// in this field. This method returns that value, if it was specified, /// this field, but clients are also able to specify _any_ name, including
/// without a file extension. /// 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 /// Note that you will likely want to prepend or append random or
/// has been sanitized so as to not to contain path components, start with /// user-specific components to the name to avoid collisions; UUIDs make for
/// `.` or `*`, or end with `:`, `>`, or `<`, making it safe for direct use /// a good "random" data.
/// as the name of a file. ///
/// See [`FileName::as_str()`] for specifics on sanitization.
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
@ -382,15 +385,30 @@ impl<'v> TempFile<'v> {
/// #[post("/", data = "<file>")] /// #[post("/", data = "<file>")]
/// async fn handle(mut file: TempFile<'_>) -> std::io::Result<()> { /// async fn handle(mut file: TempFile<'_>) -> std::io::Result<()> {
/// # let some_dir = std::env::temp_dir(); /// # let some_dir = std::env::temp_dir();
/// if let Some(name) = file.file_name() { /// if let Some(name) = file.name() {
/// // Due to Rocket's sanitization, this is safe. /// // Because of Rocket's sanitization, this is safe.
/// file.persist_to(&some_dir.join(name)).await?; /// file.persist_to(&some_dir.join(name)).await?;
/// } /// }
/// ///
/// Ok(()) /// 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 = "<file>")]
/// async fn handle(mut file: TempFile<'_>) {
/// let raw_name = file.raw_name();
/// }
/// ```
pub fn raw_name(&self) -> Option<&FileName> {
match *self { match *self {
TempFile::File { file_name, .. } => file_name, TempFile::File { file_name, .. } => file_name,
TempFile::Buffered { .. } => None TempFile::Buffered { .. } => None
@ -422,7 +440,7 @@ impl<'v> TempFile<'v> {
async fn from<'a>( async fn from<'a>(
req: &Request<'_>, req: &Request<'_>,
data: Data, data: Data,
file_name: Option<&'a str>, file_name: Option<&'a FileName>,
content_type: Option<ContentType>, content_type: Option<ContentType>,
) -> io::Result<Capped<TempFile<'a>>> { ) -> io::Result<Capped<TempFile<'a>>> {
let limit = content_type.as_ref() let limit = content_type.as_ref()
@ -461,7 +479,7 @@ impl<'v> FromFormField<'v> for Capped<TempFile<'v>> {
async fn from_data( async fn from_data(
f: DataField<'v, '_> f: DataField<'v, '_>
) -> Result<Self, Errors<'v>> { ) -> Result<Self, Errors<'v>> {
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?)
} }
} }

View File

@ -1,4 +1,4 @@
use crate::form::name::NameView; use crate::form::name::{NameView, FileName};
use crate::form::error::{Error, ErrorKind, Entity}; use crate::form::error::{Error, ErrorKind, Entity};
use crate::http::{ContentType, RawStr}; use crate::http::{ContentType, RawStr};
use crate::{Request, Data}; use crate::{Request, Data};
@ -16,62 +16,6 @@ pub struct ValueField<'r> {
pub value: &'r str, 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. /// A multipart form field with an underlying data stream.
/// ///
/// Rocket preprocesses all form fields into either [`ValueField`]s or /// 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. /// The (decoded) name of the form field.
pub name: NameView<'r>, pub name: NameView<'r>,
/// The form fields's file name. /// The form fields's file name.
pub file_name: Option<FileName<'r>>, pub file_name: Option<&'r FileName>,
/// The form field's Content-Type, as submitted, which may or may not /// The form field's Content-Type, as submitted, which may or may not
/// reflect on `data`. /// reflect on `data`.
pub content_type: ContentType, pub content_type: ContentType,

View File

@ -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<S: AsRef<str> + ?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<Item = &Key> {
struct Keys<'v>(NameView<'v>);
impl<'v> Iterator for Keys<'v> {
type Item = &'v Key;
fn next(&mut self) -> Option<Self::Item> {
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<Item = &Name> {
struct Prefixes<'v>(NameView<'v>);
impl<'v> Iterator for Prefixes<'v> {
type Item = &'v Name;
fn next(&mut self) -> Option<Self::Item> {
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<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
self.0.serialize(ser)
}
}
impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Name {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>
{
<&'a str as serde::Deserialize<'de>>::deserialize(de).map(Name::new)
}
}
impl<'a, S: AsRef<str> + ?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<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> 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<str> for Name {
fn eq(&self, other: &str) -> bool {
self == Name::new(other)
}
}
impl PartialEq<Name> 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<Name> for &str {
fn eq(&self, other: &Name) -> bool {
Name::new(self) == other
}
}
impl AsRef<Name> for str {
fn as_ref(&self) -> &Name {
Name::new(self)
}
}
impl AsRef<Name> for RawStr {
fn as_ref(&self) -> &Name {
Name::new(self)
}
}
impl Eq for Name { }
impl std::hash::Hash for Name {
fn hash<H: std::hash::Hasher>(&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<S: AsRef<str> + ?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<Item = &str> {
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<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
self.0.serialize(ser)
}
}
impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Key {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>
{
<&'a str as serde::Deserialize<'de>>::deserialize(de).map(Key::new)
}
}
impl<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> for Key {
type Output = Key;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0[index].into()
}
}
impl PartialEq<str> for Key {
fn eq(&self, other: &str) -> bool {
self == Key::new(other)
}
}
impl PartialEq<Key> for str {
fn eq(&self, other: &Key) -> bool {
Key::new(self) == other
}
}
impl<'a, S: AsRef<str> + ?Sized> From<&'a S> for &'a Key {
#[inline]
fn from(string: &'a S) -> Self {
Key::new(string)
}
}
impl AsRef<Key> for str {
fn as_ref(&self) -> &Key {
Key::new(self)
}
}
impl AsRef<Key> 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<N: Into<&'v Name>>(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<NameView<'b>> for NameView<'a> {
fn eq(&self, other: &NameView<'b>) -> bool {
self.as_name() == other.as_name()
}
}
impl<B: PartialEq<Name>> PartialEq<B> for NameView<'_> {
fn eq(&self, other: &B) -> bool {
other == self.as_name()
}
}
impl Eq for NameView<'_> { }
impl std::hash::Hash for NameView<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_name().hash(state)
}
}
impl std::borrow::Borrow<Name> 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<Item = &Key> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
serializer.serialize_str(&self.to_string())
}
}
impl<'v> From<NameView<'v>> 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<String> 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<N: AsRef<Name> + ?Sized> PartialEq<N> for NameBuf<'_> {
fn eq(&self, other: &N) -> bool {
self.keys().eq(other.as_ref().keys())
}
}
impl PartialEq<Name> for NameBuf<'_> {
fn eq(&self, other: &Name) -> bool {
self.keys().eq(other.keys())
}
}
impl PartialEq<NameBuf<'_>> for Name {
fn eq(&self, other: &NameBuf<'_>) -> bool {
self.keys().eq(other.keys())
}
}
impl PartialEq<NameBuf<'_>> for str {
fn eq(&self, other: &NameBuf<'_>) -> bool {
Name::new(self) == other
}
}
impl PartialEq<NameBuf<'_>> for &str {
fn eq(&self, other: &NameBuf<'_>) -> bool {
Name::new(self) == other
}
}
impl Eq for NameBuf<'_> { }
impl std::hash::Hash for NameBuf<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.keys().for_each(|k| k.0.hash(state))
}
}
impl indexmap::Equivalent<Name> for NameBuf<'_> {
fn equivalent(&self, key: &Name) -> bool {
self.keys().eq(key.keys())
}
}
impl indexmap::Equivalent<NameBuf<'_>> for Name {
fn equivalent(&self, key: &NameBuf<'_>) -> bool {
self.keys().eq(key.keys())
}
}

View File

@ -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<Item = &Key> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
serializer.serialize_str(&self.to_string())
}
}
impl<'v> From<NameView<'v>> 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<String> 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<N: AsRef<Name> + ?Sized> PartialEq<N> for NameBuf<'_> {
fn eq(&self, other: &N) -> bool {
self.keys().eq(other.as_ref().keys())
}
}
impl PartialEq<Name> for NameBuf<'_> {
fn eq(&self, other: &Name) -> bool {
self.keys().eq(other.keys())
}
}
impl PartialEq<NameBuf<'_>> for Name {
fn eq(&self, other: &NameBuf<'_>) -> bool {
self.keys().eq(other.keys())
}
}
impl PartialEq<NameBuf<'_>> for str {
fn eq(&self, other: &NameBuf<'_>) -> bool {
Name::new(self) == other
}
}
impl PartialEq<NameBuf<'_>> for &str {
fn eq(&self, other: &NameBuf<'_>) -> bool {
Name::new(self) == other
}
}
impl Eq for NameBuf<'_> { }
impl std::hash::Hash for NameBuf<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.keys().for_each(|k| k.hash(state))
}
}
impl indexmap::Equivalent<Name> for NameBuf<'_> {
fn equivalent(&self, key: &Name) -> bool {
self.keys().eq(key.keys())
}
}
impl indexmap::Equivalent<NameBuf<'_>> for Name {
fn equivalent(&self, key: &NameBuf<'_>) -> bool {
self.keys().eq(key.keys())
}
}

View File

@ -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<S: AsRef<str> + ?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<str> + ?Sized> From<&'a S> for &'a FileName {
#[inline]
fn from(string: &'a S) -> Self {
FileName::new(string)
}
}

View File

@ -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<S: AsRef<str> + ?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<Item = &str> {
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<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
self.0.serialize(ser)
}
}
impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Key {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>
{
<&'a str as serde::Deserialize<'de>>::deserialize(de).map(Key::new)
}
}
impl<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> for Key {
type Output = Key;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0[index].into()
}
}
impl PartialEq<str> for Key {
fn eq(&self, other: &str) -> bool {
self == Key::new(other)
}
}
impl PartialEq<Key> for str {
fn eq(&self, other: &Key) -> bool {
Key::new(self) == other
}
}
impl<'a, S: AsRef<str> + ?Sized> From<&'a S> for &'a Key {
#[inline]
fn from(string: &'a S) -> Self {
Key::new(string)
}
}
impl AsRef<Key> for str {
fn as_ref(&self) -> &Key {
Key::new(self)
}
}
impl AsRef<Key> 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)
}
}

View File

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

View File

@ -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<S: AsRef<str> + ?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<Item = &Key> {
struct Keys<'v>(NameView<'v>);
impl<'v> Iterator for Keys<'v> {
type Item = &'v Key;
fn next(&mut self) -> Option<Self::Item> {
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<Item = &Name> {
struct Prefixes<'v>(NameView<'v>);
impl<'v> Iterator for Prefixes<'v> {
type Item = &'v Name;
fn next(&mut self) -> Option<Self::Item> {
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<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
self.0.serialize(ser)
}
}
impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Name {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>
{
<&'a str as serde::Deserialize<'de>>::deserialize(de).map(Name::new)
}
}
impl<'a, S: AsRef<str> + ?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<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> 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<str> for Name {
fn eq(&self, other: &str) -> bool {
self == Name::new(other)
}
}
impl PartialEq<Name> 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<Name> for &str {
fn eq(&self, other: &Name) -> bool {
Name::new(self) == other
}
}
impl AsRef<Name> for str {
fn as_ref(&self) -> &Name {
Name::new(self)
}
}
impl AsRef<Name> for RawStr {
fn as_ref(&self) -> &Name {
Name::new(self)
}
}
impl Eq for Name { }
impl std::hash::Hash for Name {
fn hash<H: std::hash::Hasher>(&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)
}
}

View File

@ -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<N: Into<&'v Name>>(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<NameView<'b>> for NameView<'a> {
fn eq(&self, other: &NameView<'b>) -> bool {
self.as_name() == other.as_name()
}
}
impl<B: PartialEq<Name>> PartialEq<B> for NameView<'_> {
fn eq(&self, other: &B) -> bool {
other == self.as_name()
}
}
impl Eq for NameView<'_> { }
impl std::hash::Hash for NameView<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_name().hash(state)
}
}
impl std::borrow::Borrow<Name> for NameView<'_> {
fn borrow(&self) -> &Name {
self.as_name()
}
}

View File

@ -182,7 +182,7 @@ impl<'r, 'i> MultipartParser<'r, 'i> {
content_type, content_type,
request: self.request, request: self.request,
name: NameView::new(name), 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), data: Data::from(field),
}) })
} else { } else {

View File

@ -126,7 +126,6 @@ function run_benchmarks() {
popd > /dev/null 2>&1 popd > /dev/null 2>&1
} }
if [[ $1 == +* ]]; then if [[ $1 == +* ]]; then
CARGO="$CARGO $1" CARGO="$CARGO $1"
shift shift