Allow accessing raw file name from 'DataField'.

This commit is contained in:
Jonah Brüchert 2021-03-29 19:01:44 +02:00 committed by Sergio Benitez
parent 0654890e3d
commit fa1b75ba74
4 changed files with 61 additions and 29 deletions

View File

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

View File

@ -461,7 +461,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, Some(f.content_type)).await?) Ok(TempFile::from(f.request, f.data, f.file_name.and_then(|f| f.name()), Some(f.content_type)).await?)
} }
} }

View File

@ -16,6 +16,62 @@ 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
@ -25,16 +81,8 @@ pub struct ValueField<'r> {
pub struct DataField<'r, 'i> { 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 field's sanitized file name. /// The form fields's file name.
/// pub file_name: Option<FileName<'r>>,
/// ## 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, `file_name` will be `None`.
pub file_name: Option<&'r str>,
/// The form field's Content-Type, as submitted, which may or may not /// The form field's Content-Type, as submitted, which may or may not
/// reflect on `data`. /// reflect on `data`.
pub content_type: ContentType, pub content_type: ContentType,

View File

@ -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(sanitize), file_name: file_name.and_then(|name| Some(FileName::new(name))),
data: Data::from(field), data: Data::from(field),
}) })
} else { } else {
@ -204,23 +204,6 @@ impl<'r, 'i> MultipartParser<'r, 'i> {
} }
} }
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)
}
impl Buffer { impl Buffer {
pub fn new() -> Self { pub fn new() -> Self {
Buffer { Buffer {