mirror of https://github.com/fafhrd91/actix-web
Rework content_disposition.rs
This commit is contained in:
parent
6b9fa2c3d9
commit
c4e29eb1dc
10
src/fs.rs
10
src/fs.rs
|
@ -164,11 +164,7 @@ impl<C: StaticFileConfig> NamedFile<C> {
|
||||||
let disposition_type = C::content_disposition_map(ct.type_());
|
let disposition_type = C::content_disposition_map(ct.type_());
|
||||||
let cd = ContentDisposition {
|
let cd = ContentDisposition {
|
||||||
disposition: disposition_type,
|
disposition: disposition_type,
|
||||||
parameters: vec![DispositionParam::Filename(
|
parameters: vec![DispositionParam::Filename(filename.into_owned())],
|
||||||
header::Charset::Ext("UTF-8".to_owned()),
|
|
||||||
None,
|
|
||||||
filename.as_bytes().to_vec(),
|
|
||||||
)],
|
|
||||||
};
|
};
|
||||||
(ct, cd)
|
(ct, cd)
|
||||||
};
|
};
|
||||||
|
@ -991,9 +987,7 @@ mod tests {
|
||||||
let cd = ContentDisposition {
|
let cd = ContentDisposition {
|
||||||
disposition: DispositionType::Attachment,
|
disposition: DispositionType::Attachment,
|
||||||
parameters: vec![DispositionParam::Filename(
|
parameters: vec![DispositionParam::Filename(
|
||||||
header::Charset::Ext("UTF-8".to_owned()),
|
String::from("test.png")
|
||||||
None,
|
|
||||||
"test.png".as_bytes().to_vec(),
|
|
||||||
)],
|
)],
|
||||||
};
|
};
|
||||||
let mut file = NamedFile::open("tests/test.png")
|
let mut file = NamedFile::open("tests/test.png")
|
||||||
|
|
|
@ -2,17 +2,34 @@
|
||||||
//
|
//
|
||||||
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
||||||
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
||||||
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc2388.txt
|
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
||||||
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
||||||
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
||||||
|
|
||||||
use language_tags::LanguageTag;
|
|
||||||
use header;
|
use header;
|
||||||
|
use header::ExtendedValue;
|
||||||
use header::{Header, IntoHeaderValue, Writer};
|
use header::{Header, IntoHeaderValue, Writer};
|
||||||
use header::shared::Charset;
|
|
||||||
|
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
|
/// Split at the index of the first `needle` if it exists or at the end.
|
||||||
|
fn split_once<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) {
|
||||||
|
haystack.find(needle).map_or_else(
|
||||||
|
|| (haystack, ""),
|
||||||
|
|sc| {
|
||||||
|
let (first, last) = haystack.split_at(sc);
|
||||||
|
(first, last.split_at(1).1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split at the index of the first `needle` if it exists or at the end, trim the right of the
|
||||||
|
/// first part and the left of the last part.
|
||||||
|
fn split_once_and_trim<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) {
|
||||||
|
let (first, last) = split_once(haystack, needle);
|
||||||
|
(first.trim_right(), last.trim_left())
|
||||||
|
}
|
||||||
|
|
||||||
/// The implied disposition of the content of the HTTP body.
|
/// The implied disposition of the content of the HTTP body.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum DispositionType {
|
pub enum DispositionType {
|
||||||
|
@ -21,27 +38,163 @@ pub enum DispositionType {
|
||||||
/// Attachment implies that the recipient should prompt the user to save the response locally,
|
/// Attachment implies that the recipient should prompt the user to save the response locally,
|
||||||
/// rather than process it normally (as per its media type).
|
/// rather than process it normally (as per its media type).
|
||||||
Attachment,
|
Attachment,
|
||||||
/// Extension type. Should be handled by recipients the same way as Attachment
|
/// Used in *multipart/form-data* as defined in
|
||||||
Ext(String)
|
/// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name.
|
||||||
|
FormData,
|
||||||
|
/// Extension type. Should be handled by recipients the same way as Attachment
|
||||||
|
Ext(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A parameter to the disposition type.
|
impl<'a> From<&'a str> for DispositionType {
|
||||||
|
fn from(origin: &'a str) -> DispositionType {
|
||||||
|
match origin.as_bytes().to_ascii_lowercase().as_slice() {
|
||||||
|
b"inline" => DispositionType::Inline,
|
||||||
|
b"attachment" => DispositionType::Attachment,
|
||||||
|
b"form-data" => DispositionType::FormData,
|
||||||
|
_ => DispositionType::Ext(origin.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameter in [`ContentDisposition`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::http::header::DispositionParam;
|
||||||
|
///
|
||||||
|
/// let param = DispositionParam::Filename(String::from("sample.txt"));
|
||||||
|
/// assert!(param.is_filename());
|
||||||
|
/// assert_eq!(param.as_filename().unwrap(), "sample.txt");
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum DispositionParam {
|
pub enum DispositionParam {
|
||||||
/// A Filename consisting of a Charset, an optional LanguageTag, and finally a sequence of
|
/// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from
|
||||||
/// bytes representing the filename
|
/// the form.
|
||||||
Filename(Charset, Option<LanguageTag>, Vec<u8>),
|
Name(String),
|
||||||
/// Extension type consisting of token and value. Recipients should ignore unrecognized
|
/// A plain file name.
|
||||||
/// parameters.
|
Filename(String),
|
||||||
Ext(String, String)
|
/// An extended file name. It must not exist for `ContentType::Formdata` according to
|
||||||
|
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
|
||||||
|
FilenameExt(ExtendedValue),
|
||||||
|
/// An unrecognized regular parameter as defined in
|
||||||
|
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in
|
||||||
|
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should
|
||||||
|
/// ignore unrecognizable parameters.
|
||||||
|
Unknown(String, String),
|
||||||
|
/// An unrecognized extended paramater as defined in
|
||||||
|
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in
|
||||||
|
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single
|
||||||
|
/// trailling asterisk is not included. Recipients should ignore unrecognizable parameters.
|
||||||
|
UnknownExt(String, ExtendedValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `Content-Disposition` header, (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266).
|
impl DispositionParam {
|
||||||
|
/// Returns `true` if the paramater is [`Name`](DispositionParam::Name).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_name(&self) -> bool {
|
||||||
|
self.as_name().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_filename(&self) -> bool {
|
||||||
|
self.as_filename().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_filename_ext(&self) -> bool {
|
||||||
|
self.as_filename_ext().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name`
|
||||||
|
#[inline]
|
||||||
|
/// matches.
|
||||||
|
pub fn is_unknown<'a, T: AsRef<str>>(&self, name: T) -> bool {
|
||||||
|
self.as_unknown(name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the
|
||||||
|
/// `name` matches.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_unknown_ext<'a, T: AsRef<str>>(&self, name: T) -> bool {
|
||||||
|
self.as_unknown_ext(name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name if applicable.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_name<'a>(&'a self) -> Option<&'a str> {
|
||||||
|
match self {
|
||||||
|
DispositionParam::Name(ref name) => Some(name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the filename if applicable.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_filename<'a>(&'a self) -> Option<&'a str> {
|
||||||
|
match self {
|
||||||
|
&DispositionParam::Filename(ref filename) => Some(filename.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the filename* if applicable.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> {
|
||||||
|
match self {
|
||||||
|
&DispositionParam::FilenameExt(ref value) => Some(value),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value of the unrecognized regular parameter if it is
|
||||||
|
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_unknown<'a, T: AsRef<str>>(&'a self, name: T) -> Option<&'a str> {
|
||||||
|
match self {
|
||||||
|
&DispositionParam::Unknown(ref ext_name, ref value)
|
||||||
|
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
|
||||||
|
{
|
||||||
|
Some(value.as_str())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value of the unrecognized extended parameter if it is
|
||||||
|
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_unknown_ext<'a, T: AsRef<str>>(
|
||||||
|
&'a self, name: T,
|
||||||
|
) -> Option<&'a ExtendedValue> {
|
||||||
|
match self {
|
||||||
|
&DispositionParam::UnknownExt(ref ext_name, ref value)
|
||||||
|
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
|
||||||
|
{
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A *Content-Disposition* header. It is compatible to be used either as
|
||||||
|
/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body)
|
||||||
|
/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as
|
||||||
|
/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body)
|
||||||
|
/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578).
|
||||||
///
|
///
|
||||||
/// The Content-Disposition response header field is used to convey
|
/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if
|
||||||
/// additional information about how to process the response payload, and
|
/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as
|
||||||
/// also can be used to attach additional metadata, such as the filename
|
/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be
|
||||||
/// to use when saving the response payload locally.
|
/// used to attach additional metadata, such as the filename to use when saving the response payload
|
||||||
|
/// locally.
|
||||||
|
///
|
||||||
|
/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that
|
||||||
|
/// can be used on the subpart of a multipart body to give information about the field it applies to.
|
||||||
|
/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body
|
||||||
|
/// itself, *Content-Disposition* has no effect.
|
||||||
///
|
///
|
||||||
/// # ABNF
|
/// # ABNF
|
||||||
|
|
||||||
|
@ -65,88 +218,215 @@ pub enum DispositionParam {
|
||||||
/// ext-token = <the characters in token, followed by "*">
|
/// ext-token = <the characters in token, followed by "*">
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
|
||||||
|
/// *multipart/form-data*.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset};
|
/// use actix_web::http::header::{
|
||||||
|
/// Charset, ContentDisposition, DispositionParam, DispositionType,
|
||||||
|
/// ExtendedValue,
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// let cd1 = ContentDisposition {
|
/// let cd1 = ContentDisposition {
|
||||||
/// disposition: DispositionType::Attachment,
|
/// disposition: DispositionType::Attachment,
|
||||||
/// parameters: vec![DispositionParam::Filename(
|
/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||||
/// Charset::Iso_8859_1, // The character set for the bytes of the filename
|
/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename
|
||||||
/// None, // The optional language tag (see `language-tag` crate)
|
/// language_tag: None, // The optional language tag (see `language-tag` crate)
|
||||||
/// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename
|
/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename
|
||||||
/// )]
|
/// })],
|
||||||
/// };
|
/// };
|
||||||
|
/// assert!(cd1.is_attachment());
|
||||||
|
/// assert!(cd1.get_filename_ext().is_some());
|
||||||
///
|
///
|
||||||
/// let cd2 = ContentDisposition {
|
/// let cd2 = ContentDisposition {
|
||||||
/// disposition: DispositionType::Inline,
|
/// disposition: DispositionType::FormData,
|
||||||
/// parameters: vec![DispositionParam::Filename(
|
/// parameters: vec![
|
||||||
/// Charset::Ext("UTF-8".to_owned()),
|
/// DispositionParam::Name(String::from("file")),
|
||||||
/// None,
|
/// DispositionParam::Filename(String::from("bill.odt")),
|
||||||
/// "\u{2764}".as_bytes().to_vec()
|
/// ],
|
||||||
/// )]
|
|
||||||
/// };
|
/// };
|
||||||
|
/// assert_eq!(cd2.get_name(), Some("file")); // field name
|
||||||
|
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// # WARN
|
||||||
|
/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly
|
||||||
|
/// change to match local file system conventions if applicable, and do not use directory path
|
||||||
|
/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3)
|
||||||
|
/// .
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ContentDisposition {
|
pub struct ContentDisposition {
|
||||||
/// The disposition
|
/// The disposition type
|
||||||
pub disposition: DispositionType,
|
pub disposition: DispositionType,
|
||||||
/// Disposition parameters
|
/// Disposition parameters
|
||||||
pub parameters: Vec<DispositionParam>,
|
pub parameters: Vec<DispositionParam>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentDisposition {
|
impl ContentDisposition {
|
||||||
/// Parse a raw Content-Disposition header value
|
/// Parse a raw Content-Disposition header value.
|
||||||
pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, ::error::ParseError> {
|
pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, ::error::ParseError> {
|
||||||
header::from_one_raw_str(Some(hv)).and_then(|s: String| {
|
// `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible
|
||||||
let mut sections = s.split(';');
|
// ASCII characters. So `hv.as_bytes` is necessary here.
|
||||||
let disposition = match sections.next() {
|
let hv = String::from_utf8(hv.as_bytes().to_vec())
|
||||||
Some(s) => s.trim(),
|
.map_err(|_| ::error::ParseError::Header)?;
|
||||||
None => return Err(::error::ParseError::Header),
|
let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
|
||||||
};
|
if disp_type.len() == 0 {
|
||||||
|
return Err(::error::ParseError::Header);
|
||||||
|
}
|
||||||
|
let mut cd = ContentDisposition {
|
||||||
|
disposition: disp_type.into(),
|
||||||
|
parameters: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut cd = ContentDisposition {
|
while left.len() > 0 {
|
||||||
disposition: if disposition.eq_ignore_ascii_case("inline") {
|
let (param_name, new_left) = split_once_and_trim(left, '=');
|
||||||
DispositionType::Inline
|
if param_name.len() == 0 {
|
||||||
} else if disposition.eq_ignore_ascii_case("attachment") {
|
return Err(::error::ParseError::Header);
|
||||||
DispositionType::Attachment
|
|
||||||
} else {
|
|
||||||
DispositionType::Ext(disposition.to_owned())
|
|
||||||
},
|
|
||||||
parameters: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for section in sections {
|
|
||||||
let mut parts = section.splitn(2, '=');
|
|
||||||
|
|
||||||
let key = if let Some(key) = parts.next() {
|
|
||||||
key.trim()
|
|
||||||
} else {
|
|
||||||
return Err(::error::ParseError::Header);
|
|
||||||
};
|
|
||||||
|
|
||||||
let val = if let Some(val) = parts.next() {
|
|
||||||
val.trim()
|
|
||||||
} else {
|
|
||||||
return Err(::error::ParseError::Header);
|
|
||||||
};
|
|
||||||
|
|
||||||
cd.parameters.push(
|
|
||||||
if key.eq_ignore_ascii_case("filename") {
|
|
||||||
DispositionParam::Filename(
|
|
||||||
Charset::Ext("UTF-8".to_owned()), None,
|
|
||||||
val.trim_matches('"').as_bytes().to_owned())
|
|
||||||
} else if key.eq_ignore_ascii_case("filename*") {
|
|
||||||
let extended_value = try!(header::parse_extended_value(val));
|
|
||||||
DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value)
|
|
||||||
} else {
|
|
||||||
DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned())
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
left = new_left;
|
||||||
|
if param_name.ends_with('*') {
|
||||||
|
// extended parameters
|
||||||
|
let (param_name, _) = param_name.split_at(param_name.len() - 1); // trim asterisk
|
||||||
|
let (ext_value, new_left) = split_once_and_trim(left, ';');
|
||||||
|
left = new_left;
|
||||||
|
let ext_value = header::parse_extended_value(ext_value)?;
|
||||||
|
cd.parameters
|
||||||
|
.push(match param_name.to_ascii_lowercase().as_str() {
|
||||||
|
"filename" => DispositionParam::FilenameExt(ext_value),
|
||||||
|
_ => DispositionParam::UnknownExt(
|
||||||
|
param_name.to_owned(),
|
||||||
|
ext_value,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// regular parameters
|
||||||
|
let value = if left.starts_with('\"') {
|
||||||
|
// quoted-string: defined in RFC6266 -> RFC2616 Section 3.6
|
||||||
|
let mut escaping = false;
|
||||||
|
let mut quoted_string = vec![];
|
||||||
|
let mut end = None;
|
||||||
|
// search for closing quote
|
||||||
|
for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
|
||||||
|
if escaping {
|
||||||
|
escaping = false;
|
||||||
|
quoted_string.push(c);
|
||||||
|
} else {
|
||||||
|
if c == 0x5c // backslash
|
||||||
|
{
|
||||||
|
escaping = true;
|
||||||
|
} else if c == 0x22 // double quote
|
||||||
|
{
|
||||||
|
end = Some(i + 1); // cuz skipped 1 for the leading quote
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
quoted_string.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
left = left.split_at(end.ok_or(::error::ParseError::Header)? + 1).1;
|
||||||
|
left = split_once(left, ';').1.trim_left();
|
||||||
|
// In fact, tt should not be Err if the above code is correct.
|
||||||
|
let quoted_string = String::from_utf8(quoted_string)
|
||||||
|
.map_err(|_| ::error::ParseError::Header)?;
|
||||||
|
quoted_string
|
||||||
|
} else {
|
||||||
|
// token: won't contains semicolon according to RFC 2616 Section 2.2
|
||||||
|
let (token, new_left) = split_once_and_trim(left, ';');
|
||||||
|
left = new_left;
|
||||||
|
token.to_owned()
|
||||||
|
};
|
||||||
|
if value.len() == 0 {
|
||||||
|
return Err(::error::ParseError::Header);
|
||||||
|
}
|
||||||
|
cd.parameters
|
||||||
|
.push(match param_name.to_ascii_lowercase().as_str() {
|
||||||
|
"name" => DispositionParam::Name(value),
|
||||||
|
"filename" => DispositionParam::Filename(value),
|
||||||
|
_ => DispositionParam::Unknown(param_name.to_owned(), value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cd)
|
Ok(cd)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if it is [`Inline`](DispositionType::Inline).
|
||||||
|
pub fn is_inline(&self) -> bool {
|
||||||
|
match self.disposition {
|
||||||
|
DispositionType::Inline => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if it is [`Attachment`](DispositionType::Attachment).
|
||||||
|
pub fn is_attachment(&self) -> bool {
|
||||||
|
match self.disposition {
|
||||||
|
DispositionType::Attachment => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if it is [`FormData`](DispositionType::FormData).
|
||||||
|
pub fn is_form_data(&self) -> bool {
|
||||||
|
match self.disposition {
|
||||||
|
DispositionType::FormData => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches.
|
||||||
|
pub fn is_ext<T: AsRef<str>>(&self, disp_type: T) -> bool {
|
||||||
|
match self.disposition {
|
||||||
|
DispositionType::Ext(ref t)
|
||||||
|
if t.eq_ignore_ascii_case(disp_type.as_ref()) =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of *name* if exists.
|
||||||
|
pub fn get_name<'a>(&'a self) -> Option<&'a str> {
|
||||||
|
self.parameters.iter().filter_map(|p| p.as_name()).nth(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of *filename* if exists.
|
||||||
|
pub fn get_filename<'a>(&'a self) -> Option<&'a str> {
|
||||||
|
self.parameters
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| p.as_filename())
|
||||||
|
.nth(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of *filename\** if exists.
|
||||||
|
pub fn get_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> {
|
||||||
|
self.parameters
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| p.as_filename_ext())
|
||||||
|
.nth(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of the parameter which the `name` matches.
|
||||||
|
pub fn get_unknown<'a, T: AsRef<str>>(&'a self, name: T) -> Option<&'a str> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
self.parameters
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| p.as_unknown(name))
|
||||||
|
.nth(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of the extended parameter which the `name` matches.
|
||||||
|
pub fn get_unknown_ext<'a, T: AsRef<str>>(
|
||||||
|
&'a self, name: T,
|
||||||
|
) -> Option<&'a ExtendedValue> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
self.parameters
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| p.as_unknown_ext(name))
|
||||||
|
.nth(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,67 +454,70 @@ impl Header for ContentDisposition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ContentDisposition {
|
impl fmt::Display for DispositionType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.disposition {
|
match self {
|
||||||
DispositionType::Inline => try!(write!(f, "inline")),
|
DispositionType::Inline => write!(f, "inline"),
|
||||||
DispositionType::Attachment => try!(write!(f, "attachment")),
|
DispositionType::Attachment => write!(f, "attachment"),
|
||||||
DispositionType::Ext(ref s) => try!(write!(f, "{}", s)),
|
DispositionType::FormData => write!(f, "form-data"),
|
||||||
|
DispositionType::Ext(ref s) => write!(f, "{}", s),
|
||||||
}
|
}
|
||||||
for param in &self.parameters {
|
}
|
||||||
match *param {
|
}
|
||||||
DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => {
|
|
||||||
let mut use_simple_format: bool = false;
|
impl fmt::Display for DispositionParam {
|
||||||
if opt_lang.is_none() {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if let Charset::Ext(ref ext) = *charset {
|
// TODO: More special charaters in filename should be escaped.
|
||||||
if ext.eq_ignore_ascii_case("utf-8") {
|
match self {
|
||||||
use_simple_format = true;
|
DispositionParam::Name(ref value) => {
|
||||||
}
|
write!(f, "name={}", &value.replace('\"', "\\\""))
|
||||||
}
|
}
|
||||||
}
|
DispositionParam::Filename(ref value) => {
|
||||||
if use_simple_format {
|
write!(f, "filename=\"{}\"", &value.replace('\"', "\\\""))
|
||||||
use std::str;
|
}
|
||||||
try!(write!(f, "; filename=\"{}\"",
|
DispositionParam::Unknown(ref name, ref value) => {
|
||||||
match str::from_utf8(bytes) {
|
write!(f, "{}=\"{}\"", name, &value.replace('\"', "\\\""))
|
||||||
Ok(s) => s,
|
}
|
||||||
Err(_) => return Err(fmt::Error),
|
DispositionParam::FilenameExt(ref ext_value) => {
|
||||||
}));
|
write!(f, "filename*={}", ext_value)
|
||||||
} else {
|
}
|
||||||
try!(write!(f, "; filename*={}'", charset));
|
DispositionParam::UnknownExt(ref name, ref ext_value) => {
|
||||||
if let Some(ref lang) = *opt_lang {
|
write!(f, "{}*={}", name, ext_value)
|
||||||
try!(write!(f, "{}", lang));
|
|
||||||
};
|
|
||||||
try!(write!(f, "'"));
|
|
||||||
try!(header::http_percent_encode(f, bytes))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContentDisposition {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.disposition)?;
|
||||||
|
self.parameters
|
||||||
|
.iter()
|
||||||
|
.map(|param| write!(f, "; {}", param))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ContentDisposition,DispositionType,DispositionParam};
|
use super::{ContentDisposition, DispositionParam, DispositionType};
|
||||||
use header::HeaderValue;
|
|
||||||
use header::shared::Charset;
|
use header::shared::Charset;
|
||||||
|
use header::{ExtendedValue, HeaderValue};
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_raw() {
|
fn test_from_raw_basic() {
|
||||||
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
|
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
|
||||||
|
|
||||||
let a = HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data; dummy=3; name=upload; filename=\"sample.png\"",
|
||||||
|
);
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let b = ContentDisposition {
|
let b = ContentDisposition {
|
||||||
disposition: DispositionType::Ext("form-data".to_owned()),
|
disposition: DispositionType::FormData,
|
||||||
parameters: vec![
|
parameters: vec![
|
||||||
DispositionParam::Ext("dummy".to_owned(), "3".to_owned()),
|
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||||
DispositionParam::Ext("name".to_owned(), "upload".to_owned()),
|
DispositionParam::Name("upload".to_owned()),
|
||||||
DispositionParam::Filename(
|
DispositionParam::Filename("sample.png".to_owned()),
|
||||||
Charset::Ext("UTF-8".to_owned()),
|
],
|
||||||
None,
|
|
||||||
"sample.png".bytes().collect()) ]
|
|
||||||
};
|
};
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
@ -242,44 +525,327 @@ mod tests {
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let b = ContentDisposition {
|
let b = ContentDisposition {
|
||||||
disposition: DispositionType::Attachment,
|
disposition: DispositionType::Attachment,
|
||||||
parameters: vec![
|
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
|
||||||
DispositionParam::Filename(
|
|
||||||
Charset::Ext("UTF-8".to_owned()),
|
|
||||||
None,
|
|
||||||
"image.jpg".bytes().collect()) ]
|
|
||||||
};
|
};
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
|
|
||||||
let a = HeaderValue::from_static("attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
let a = HeaderValue::from_static("inline; filename=image.jpg");
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Inline,
|
||||||
|
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
|
||||||
|
);
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let b = ContentDisposition {
|
let b = ContentDisposition {
|
||||||
disposition: DispositionType::Attachment,
|
disposition: DispositionType::Attachment,
|
||||||
parameters: vec![
|
parameters: vec![DispositionParam::Unknown(
|
||||||
DispositionParam::Filename(
|
String::from("creation-date"),
|
||||||
Charset::Ext("UTF-8".to_owned()),
|
"Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
|
||||||
None,
|
)],
|
||||||
vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20,
|
|
||||||
0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ]
|
|
||||||
};
|
};
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_display() {
|
fn test_from_raw_extended() {
|
||||||
let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
|
let a = HeaderValue::from_static(
|
||||||
|
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Attachment,
|
||||||
|
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
charset: Charset::Ext(String::from("UTF-8")),
|
||||||
|
language_tag: None,
|
||||||
|
value: vec![
|
||||||
|
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
|
||||||
|
b'r', b'a', b't', b'e', b's',
|
||||||
|
],
|
||||||
|
})],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Attachment,
|
||||||
|
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
charset: Charset::Ext(String::from("UTF-8")),
|
||||||
|
language_tag: None,
|
||||||
|
value: vec![
|
||||||
|
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
|
||||||
|
b'r', b'a', b't', b'e', b's',
|
||||||
|
],
|
||||||
|
})],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_extra_whitespace() {
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
|
||||||
|
DispositionParam::Name("upload".to_owned()),
|
||||||
|
DispositionParam::Filename("sample.png".to_owned()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_unordered() {
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
|
||||||
|
// Actually, a trailling semolocon is not compliant. But it is fine to accept.
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||||
|
DispositionParam::Filename("sample.png".to_owned()),
|
||||||
|
DispositionParam::Name("upload".to_owned()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a = HeaderValue::from_str(
|
||||||
|
"attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
|
||||||
|
).unwrap();
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Attachment,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
charset: Charset::Iso_8859_1,
|
||||||
|
language_tag: None,
|
||||||
|
value: b"foo-\xe4.html".to_vec(),
|
||||||
|
}),
|
||||||
|
DispositionParam::Filename("foo-ä.html".to_owned()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_only_disp() {
|
||||||
|
let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment"))
|
||||||
|
.unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Attachment,
|
||||||
|
parameters: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a =
|
||||||
|
ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Inline,
|
||||||
|
parameters: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a = ContentDisposition::from_raw(&HeaderValue::from_static(
|
||||||
|
"unknown-disp-param",
|
||||||
|
)).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Ext(String::from("unknown-disp-param")),
|
||||||
|
parameters: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_raw_with_mixed_case() {
|
||||||
|
let a = HeaderValue::from_str(
|
||||||
|
"InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
|
||||||
|
).unwrap();
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::Inline,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
charset: Charset::Iso_8859_1,
|
||||||
|
language_tag: None,
|
||||||
|
value: b"foo-\xe4.html".to_vec(),
|
||||||
|
}),
|
||||||
|
DispositionParam::Filename("foo-ä.html".to_owned()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_raw_with_unicode() {
|
||||||
|
/* RFC7578 Section 4.2:
|
||||||
|
Some commonly deployed systems use multipart/form-data with file names directly encoded
|
||||||
|
including octets outside the US-ASCII range. The encoding used for the file names is
|
||||||
|
typically UTF-8, although HTML forms will use the charset associated with the form.
|
||||||
|
|
||||||
|
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
|
||||||
|
(And now, only UTF-8 is handled by this implementation.)
|
||||||
|
*/
|
||||||
|
let a =
|
||||||
|
HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
|
||||||
|
.unwrap();
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Name(String::from("upload")),
|
||||||
|
DispositionParam::Filename(String::from("文件.webp")),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a =
|
||||||
|
HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap();
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Name(String::from("upload")),
|
||||||
|
DispositionParam::Filename(String::from(
|
||||||
|
"余固知謇謇之為患兮,忍而不能舍也.pptx",
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_escape() {
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||||
|
DispositionParam::Name("upload".to_owned()),
|
||||||
|
DispositionParam::Filename(
|
||||||
|
['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
|
||||||
|
.iter()
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_semicolon() {
|
||||||
|
let a =
|
||||||
|
HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![DispositionParam::Filename(String::from(
|
||||||
|
"A semicolon here;.pdf",
|
||||||
|
))],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_raw_uncessary_percent_decode() {
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded!
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Name("photo".to_owned()),
|
||||||
|
DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
|
||||||
|
let a = HeaderValue::from_static(
|
||||||
|
"form-data; name=photo; filename=\"%74%65%73%74.png\"",
|
||||||
|
);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
let b = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Name("photo".to_owned()),
|
||||||
|
DispositionParam::Filename(String::from("%74%65%73%74.png")),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display_extended() {
|
||||||
|
let as_string =
|
||||||
|
"attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
|
||||||
let a = HeaderValue::from_static(as_string);
|
let a = HeaderValue::from_static(as_string);
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let display_rendered = format!("{}",a);
|
let display_rendered = format!("{}", a);
|
||||||
assert_eq!(as_string, display_rendered);
|
assert_eq!(as_string, display_rendered);
|
||||||
|
|
||||||
let a = HeaderValue::from_static("attachment; filename*=UTF-8''black%20and%20white.csv");
|
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
|
||||||
let display_rendered = format!("{}",a);
|
|
||||||
assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered);
|
|
||||||
|
|
||||||
let a = HeaderValue::from_static("attachment; filename=colourful.csv");
|
let a = HeaderValue::from_static("attachment; filename=colourful.csv");
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let display_rendered = format!("{}",a);
|
let display_rendered = format!("{}", a);
|
||||||
assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered);
|
assert_eq!(
|
||||||
|
"attachment; filename=\"colourful.csv\"".to_owned(),
|
||||||
|
display_rendered
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display_quote() {
|
||||||
|
let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
|
||||||
|
as_string
|
||||||
|
.find(['\\', '\"'].iter().collect::<String>().as_str())
|
||||||
|
.unwrap(); // ensure `\"` is there
|
||||||
|
let a = HeaderValue::from_static(as_string);
|
||||||
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
|
println!("\n\n\n{:?}", a);
|
||||||
|
let display_rendered = format!("{}", a);
|
||||||
|
assert_eq!(as_string, display_rendered);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_param_methods() {
|
||||||
|
let param = DispositionParam::Filename(String::from("sample.txt"));
|
||||||
|
assert!(param.is_filename());
|
||||||
|
assert_eq!(param.as_filename().unwrap(), "sample.txt");
|
||||||
|
|
||||||
|
let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
|
||||||
|
assert!(param.is_unknown("foo"));
|
||||||
|
assert_eq!(param.as_unknown("fOo"), Some("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_disposition_methods() {
|
||||||
|
let cd = ContentDisposition {
|
||||||
|
disposition: DispositionType::FormData,
|
||||||
|
parameters: vec![
|
||||||
|
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||||
|
DispositionParam::Name("upload".to_owned()),
|
||||||
|
DispositionParam::Filename("sample.png".to_owned()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(cd.get_name(), Some("upload"));
|
||||||
|
assert_eq!(cd.get_unknown("dummy"), Some("3"));
|
||||||
|
assert_eq!(cd.get_filename(), Some("sample.png"));
|
||||||
|
assert_eq!(cd.get_unknown_ext("dummy"), None);
|
||||||
|
assert_eq!(cd.get_unknown("duMMy"), Some("3"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,8 +263,10 @@ where
|
||||||
|
|
||||||
// From hyper v0.11.27 src/header/parsing.rs
|
// From hyper v0.11.27 src/header/parsing.rs
|
||||||
|
|
||||||
/// An extended header parameter value (i.e., tagged with a character set and optionally,
|
/// The value part of an extended parameter consisting of three parts:
|
||||||
/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
|
||||||
|
/// and a character sequence representing the actual value (`value`), separated by single quote
|
||||||
|
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ExtendedValue {
|
pub struct ExtendedValue {
|
||||||
/// The character set that is used to encode the `value` to a string.
|
/// The character set that is used to encode the `value` to a string.
|
||||||
|
|
|
@ -758,11 +758,11 @@ mod tests {
|
||||||
let cd = field.content_disposition().unwrap();
|
let cd = field.content_disposition().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cd.disposition,
|
cd.disposition,
|
||||||
DispositionType::Ext("form-data".into())
|
DispositionType::FormData
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cd.parameters[0],
|
cd.parameters[0],
|
||||||
DispositionParam::Ext("name".into(), "file".into())
|
DispositionParam::Name("file".into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||||
|
|
Loading…
Reference in New Issue