mirror of https://github.com/rwf2/Rocket.git
parent
fa1b75ba74
commit
579508d58f
|
@ -579,7 +579,7 @@ fn test_multipart() {
|
|||
#[rocket::post("/", data = "<form>")]
|
||||
fn form(form: Form<MyForm>) {
|
||||
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();
|
||||
|
|
|
@ -22,7 +22,6 @@ all-features = true
|
|||
default = []
|
||||
tls = ["rocket_http/tls"]
|
||||
secrets = ["rocket_http/private-cookies"]
|
||||
trusted_client = []
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.0"
|
||||
|
|
|
@ -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<ContentType>,
|
||||
path: Either<TempPath, PathBuf>,
|
||||
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 = "<file>")]
|
||||
/// 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 = "<file>")]
|
||||
/// 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<ContentType>,
|
||||
) -> io::Result<Capped<TempFile<'a>>> {
|
||||
let limit = content_type.as_ref()
|
||||
|
@ -461,7 +479,7 @@ impl<'v> FromFormField<'v> for Capped<TempFile<'v>> {
|
|||
async fn from_data(
|
||||
f: DataField<'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?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FileName<'r>>,
|
||||
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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -126,7 +126,6 @@ function run_benchmarks() {
|
|||
popd > /dev/null 2>&1
|
||||
}
|
||||
|
||||
|
||||
if [[ $1 == +* ]]; then
|
||||
CARGO="$CARGO $1"
|
||||
shift
|
||||
|
|
Loading…
Reference in New Issue