mirror of https://github.com/rwf2/Rocket.git
parent
fa1b75ba74
commit
579508d58f
|
@ -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();
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue