2021-12-01 19:42:02 +00:00
//! The `Content-Disposition` header and associated types.
2021-01-15 05:38:50 +00:00
//!
2021-12-01 19:42:02 +00:00
//! # References
//! - "The Content-Disposition Header Field":
//! <https://datatracker.ietf.org/doc/html/rfc2183>
//! - "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)":
//! <https://datatracker.ietf.org/doc/html/rfc6266>
//! - "Returning Values from Forms: multipart/form-data":
//! <https://datatracker.ietf.org/doc/html/rfc7578>
//! - Browser conformance tests at: <http://greenbytes.de/tech/tc2231/>
//! - IANA assignment: <http://www.iana.org/assignments/cont-disp/cont-disp.xhtml>
2019-02-07 21:24:24 +00:00
2022-07-22 19:19:02 +00:00
use std ::fmt ::{ self , Write } ;
2021-02-27 21:55:50 +00:00
use once_cell ::sync ::Lazy ;
2019-02-07 21:24:24 +00:00
use regex ::Regex ;
2021-12-13 16:08:08 +00:00
use super ::{ ExtendedValue , Header , TryIntoHeaderValue , Writer } ;
2021-04-16 19:28:21 +00:00
use crate ::http ::header ;
2019-02-07 21:24:24 +00:00
/// Split at the index of the first `needle` if it exists or at the end.
fn split_once ( haystack : & str , needle : char ) -> ( & str , & 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 ( haystack : & str , needle : char ) -> ( & str , & str ) {
let ( first , last ) = split_once ( haystack , needle ) ;
2019-03-07 05:12:35 +00:00
( first . trim_end ( ) , last . trim_start ( ) )
2019-02-07 21:24:24 +00:00
}
/// The implied disposition of the content of the HTTP body.
2022-07-23 15:26:48 +00:00
#[ derive(Debug, Clone, PartialEq, Eq) ]
2019-02-07 21:24:24 +00:00
pub enum DispositionType {
2021-11-17 17:43:24 +00:00
/// Inline implies default processing.
2019-02-07 21:24:24 +00:00
Inline ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
/// Attachment implies that the recipient should prompt the user to save the response locally,
/// rather than process it normally (as per its media type).
Attachment ,
2021-11-17 17:43:24 +00:00
2021-12-01 19:42:02 +00:00
/// Used in *multipart/form-data* as defined in
/// [RFC 7578](https://datatracker.ietf.org/doc/html/rfc7578) to carry the field name and
/// optional filename.
2019-02-07 21:24:24 +00:00
FormData ,
2021-11-17 17:43:24 +00:00
/// Extension type. Should be handled by recipients the same way as Attachment.
2019-02-07 21:24:24 +00:00
Ext ( String ) ,
}
impl < ' a > From < & ' a str > for DispositionType {
fn from ( origin : & ' a str ) -> DispositionType {
if origin . eq_ignore_ascii_case ( " inline " ) {
DispositionType ::Inline
} else if origin . eq_ignore_ascii_case ( " attachment " ) {
DispositionType ::Attachment
} else if origin . eq_ignore_ascii_case ( " form-data " ) {
DispositionType ::FormData
} else {
DispositionType ::Ext ( origin . to_owned ( ) )
}
}
}
/// Parameter in [`ContentDisposition`].
///
/// # Examples
/// ```
2021-04-01 15:42:18 +00:00
/// use actix_web::http::header::DispositionParam;
2019-02-07 21:24:24 +00:00
///
/// let param = DispositionParam::Filename(String::from("sample.txt"));
/// assert!(param.is_filename());
/// assert_eq!(param.as_filename().unwrap(), "sample.txt");
/// ```
2022-09-13 00:19:25 +00:00
#[ derive(Debug, Clone, PartialEq, Eq) ]
2019-07-17 07:55:44 +00:00
#[ allow(clippy::large_enum_variant) ]
2019-02-07 21:24:24 +00:00
pub enum DispositionParam {
/// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from
/// the form.
Name ( String ) ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
/// A plain file name.
2019-11-06 14:08:37 +00:00
///
2021-12-01 19:42:02 +00:00
/// It is [not supposed](https://datatracker.ietf.org/doc/html/rfc6266#appendix-D) to contain
/// any non-ASCII characters when used in a *Content-Disposition* HTTP response header, where
2019-11-06 14:08:37 +00:00
/// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead
/// in case there are Unicode characters in file names.
2019-02-07 21:24:24 +00:00
Filename ( String ) ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
/// An extended file name. It must not exist for `ContentType::Formdata` according to
2021-12-01 19:42:02 +00:00
/// [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2).
2019-02-07 21:24:24 +00:00
FilenameExt ( ExtendedValue ) ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
/// An unrecognized regular parameter as defined in
2021-12-01 19:42:02 +00:00
/// [RFC 5987 §3.2.1](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1) as
/// `reg-parameter`, in
/// [RFC 6266 §4.1](https://datatracker.ietf.org/doc/html/rfc6266#section-4.1) as
/// `token "=" value`. Recipients should ignore unrecognizable parameters.
2019-02-07 21:24:24 +00:00
Unknown ( String , String ) ,
2021-11-17 17:43:24 +00:00
2020-04-21 03:09:35 +00:00
/// An unrecognized extended parameter as defined in
2021-12-01 19:42:02 +00:00
/// [RFC 5987 §3.2.1](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1) as
/// `ext-parameter`, in
/// [RFC 6266 §4.1](https://datatracker.ietf.org/doc/html/rfc6266#section-4.1) as
/// `ext-token "=" ext-value`. The single trailing asterisk is not included. Recipients should
/// ignore unrecognizable parameters.
2019-02-07 21:24:24 +00:00
UnknownExt ( String , ExtendedValue ) ,
}
impl DispositionParam {
2020-04-21 03:09:35 +00:00
/// Returns `true` if the parameter is [`Name`](DispositionParam::Name).
2019-02-07 21:24:24 +00:00
#[ inline ]
pub fn is_name ( & self ) -> bool {
self . as_name ( ) . is_some ( )
}
2020-04-21 03:09:35 +00:00
/// Returns `true` if the parameter is [`Filename`](DispositionParam::Filename).
2019-02-07 21:24:24 +00:00
#[ inline ]
pub fn is_filename ( & self ) -> bool {
self . as_filename ( ) . is_some ( )
}
2020-04-21 03:09:35 +00:00
/// Returns `true` if the parameter is [`FilenameExt`](DispositionParam::FilenameExt).
2019-02-07 21:24:24 +00:00
#[ inline ]
pub fn is_filename_ext ( & self ) -> bool {
self . as_filename_ext ( ) . is_some ( )
}
2020-04-21 03:09:35 +00:00
/// Returns `true` if the parameter is [`Unknown`](DispositionParam::Unknown) and the `name`
2019-02-07 21:24:24 +00:00
#[ inline ]
/// matches.
pub fn is_unknown < T : AsRef < str > > ( & self , name : T ) -> bool {
self . as_unknown ( name ) . is_some ( )
}
2020-04-21 03:09:35 +00:00
/// Returns `true` if the parameter is [`UnknownExt`](DispositionParam::UnknownExt) and the
2019-02-07 21:24:24 +00:00
/// `name` matches.
#[ inline ]
pub fn is_unknown_ext < T : AsRef < str > > ( & self , name : T ) -> bool {
self . as_unknown_ext ( name ) . is_some ( )
}
/// Returns the name if applicable.
#[ inline ]
pub fn as_name ( & self ) -> Option < & str > {
match self {
DispositionParam ::Name ( ref name ) = > Some ( name . as_str ( ) ) ,
_ = > None ,
}
}
/// Returns the filename if applicable.
#[ inline ]
pub fn as_filename ( & self ) -> Option < & str > {
match self {
DispositionParam ::Filename ( ref filename ) = > Some ( filename . as_str ( ) ) ,
_ = > None ,
}
}
/// Returns the filename* if applicable.
#[ inline ]
pub fn as_filename_ext ( & self ) -> Option < & 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 < T : AsRef < str > > ( & self , name : T ) -> Option < & 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 < T : AsRef < str > > ( & self , name : T ) -> Option < & 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
2021-12-01 19:42:02 +00:00
/// [a response header for the main body](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#as_a_response_header_for_the_main_body)
2021-12-02 03:45:04 +00:00
/// as (re)defined in [RFC 6266](https://datatracker.ietf.org/doc/html/rfc6266), or as
2019-02-07 21:24:24 +00:00
/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body)
2021-12-02 03:45:04 +00:00
/// as (re)defined in [RFC 7587](https://datatracker.ietf.org/doc/html/rfc7578).
2019-02-07 21:24:24 +00:00
///
/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if
/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as
/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be
/// 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
2021-12-02 15:25:39 +00:00
/// ```plain
2019-02-07 21:24:24 +00:00
/// content-disposition = "Content-Disposition" ":"
/// disposition-type *( ";" disposition-parm )
///
/// disposition-type = "inline" | "attachment" | disp-ext-type
/// ; case-insensitive
///
/// disp-ext-type = token
///
/// disposition-parm = filename-parm | disp-ext-parm
///
/// filename-parm = "filename" "=" value
/// | "filename*" "=" ext-value
///
/// disp-ext-parm = token "=" value
/// | ext-token "=" ext-value
///
/// ext-token = <the characters in token, followed by "*">
/// ```
///
2019-11-06 14:08:37 +00:00
/// # Note
2021-12-01 19:42:02 +00:00
/// *filename* is [not supposed](https://datatracker.ietf.org/doc/html/rfc6266#appendix-D) to
/// contain any non-ASCII characters when used in a *Content-Disposition* HTTP response header,
/// where filename* with charset UTF-8 may be used instead in case there are Unicode characters in
/// file names. Filename is [acceptable](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2)
/// to be UTF-8 encoded directly in a *Content-Disposition* header for
/// *multipart/form-data*, though.
2019-11-06 14:08:37 +00:00
///
2021-12-01 19:42:02 +00:00
/// *filename* [must not](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2) be used within
2019-02-07 21:24:24 +00:00
/// *multipart/form-data*.
///
2021-12-01 19:42:02 +00:00
/// # Examples
2019-02-07 21:24:24 +00:00
/// ```
2021-04-01 15:42:18 +00:00
/// use actix_web::http::header::{
2019-02-07 21:24:24 +00:00
/// Charset, ContentDisposition, DispositionParam, DispositionType,
/// ExtendedValue,
/// };
///
/// let cd1 = ContentDisposition {
/// disposition: DispositionType::Attachment,
/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename
/// language_tag: None, // The optional language tag (see `language-tag` crate)
/// 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 {
/// disposition: DispositionType::FormData,
/// parameters: vec![
/// DispositionParam::Name(String::from("file")),
/// DispositionParam::Filename(String::from("bill.odt")),
/// ],
/// };
/// assert_eq!(cd2.get_name(), Some("file")); // field name
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
2019-11-06 14:08:37 +00:00
///
/// // HTTP response header with Unicode characters in file names
/// let cd3 = ContentDisposition {
/// disposition: DispositionType::Attachment,
/// parameters: vec![
/// DispositionParam::FilenameExt(ExtendedValue {
/// charset: Charset::Ext(String::from("UTF-8")),
/// language_tag: None,
/// value: String::from("\u{1f600}.svg").into_bytes(),
/// }),
/// // fallback for better compatibility
/// DispositionParam::Filename(String::from("Grinning-Face-Emoji.svg"))
/// ],
/// };
/// assert_eq!(cd3.get_filename_ext().map(|ev| ev.value.as_ref()),
/// Some("\u{1f600}.svg".as_bytes()));
2019-02-07 21:24:24 +00:00
/// ```
///
2020-09-11 11:09:52 +00:00
/// # Security Note
2019-02-07 21:24:24 +00:00
/// 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
2021-12-02 03:45:04 +00:00
/// information that may be present.
/// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3).
2022-09-13 00:19:25 +00:00
#[ derive(Debug, Clone, PartialEq, Eq) ]
2019-02-07 21:24:24 +00:00
pub struct ContentDisposition {
/// The disposition type
pub disposition : DispositionType ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
/// Disposition parameters
pub parameters : Vec < DispositionParam > ,
}
impl ContentDisposition {
2022-09-13 00:19:25 +00:00
/// Constructs a Content-Disposition header suitable for downloads.
///
/// # Examples
/// ```
/// use actix_web::http::header::{ContentDisposition, TryIntoHeaderValue as _};
///
/// let cd = ContentDisposition::attachment("files.zip");
///
/// let cd_val = cd.try_into_value().unwrap();
/// assert_eq!(cd_val, "attachment; filename=\"files.zip\"");
/// ```
pub fn attachment ( filename : impl Into < String > ) -> Self {
Self {
disposition : DispositionType ::Attachment ,
parameters : vec ! [ DispositionParam ::Filename ( filename . into ( ) ) ] ,
}
}
2019-02-07 21:24:24 +00:00
/// Parse a raw Content-Disposition header value.
pub fn from_raw ( hv : & header ::HeaderValue ) -> Result < Self , crate ::error ::ParseError > {
// `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible
// ASCII characters. So `hv.as_bytes` is necessary here.
let hv = String ::from_utf8 ( hv . as_bytes ( ) . to_vec ( ) )
. map_err ( | _ | crate ::error ::ParseError ::Header ) ? ;
2022-09-13 00:19:25 +00:00
2019-02-07 21:24:24 +00:00
let ( disp_type , mut left ) = split_once_and_trim ( hv . as_str ( ) . trim ( ) , ';' ) ;
if disp_type . is_empty ( ) {
return Err ( crate ::error ::ParseError ::Header ) ;
}
2022-09-13 00:19:25 +00:00
2019-02-07 21:24:24 +00:00
let mut cd = ContentDisposition {
disposition : disp_type . into ( ) ,
parameters : Vec ::new ( ) ,
} ;
while ! left . is_empty ( ) {
let ( param_name , new_left ) = split_once_and_trim ( left , '=' ) ;
if param_name . is_empty ( ) | | param_name = = " * " | | new_left . is_empty ( ) {
return Err ( crate ::error ::ParseError ::Header ) ;
}
left = new_left ;
2020-12-28 00:44:15 +00:00
if let Some ( param_name ) = param_name . strip_suffix ( '*' ) {
2019-02-07 21:24:24 +00:00
// extended parameters
let ( ext_value , new_left ) = split_once_and_trim ( left , ';' ) ;
left = new_left ;
let ext_value = header ::parse_extended_value ( ext_value ) ? ;
let param = if param_name . eq_ignore_ascii_case ( " filename " ) {
DispositionParam ::FilenameExt ( ext_value )
} else {
DispositionParam ::UnknownExt ( param_name . to_owned ( ) , ext_value )
} ;
cd . parameters . push ( param ) ;
} else {
// regular parameters
let value = if left . starts_with ( '\"' ) {
2021-12-01 19:42:02 +00:00
// quoted-string: defined in RFC 6266 -> RFC 2616 Section 3.6
2019-02-07 21:24:24 +00:00
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 [ end . ok_or ( crate ::error ::ParseError ::Header ) ? + 1 .. ] ;
2019-03-07 05:12:35 +00:00
left = split_once ( left , ';' ) . 1. trim_start ( ) ;
2019-02-07 21:24:24 +00:00
// In fact, it should not be Err if the above code is correct.
String ::from_utf8 ( quoted_string )
. map_err ( | _ | crate ::error ::ParseError ::Header ) ?
} 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 ;
2019-11-06 14:08:37 +00:00
if token . is_empty ( ) {
// quoted-string can be empty, but token cannot be empty
return Err ( crate ::error ::ParseError ::Header ) ;
}
2019-02-07 21:24:24 +00:00
token . to_owned ( )
} ;
let param = if param_name . eq_ignore_ascii_case ( " name " ) {
DispositionParam ::Name ( value )
} else if param_name . eq_ignore_ascii_case ( " filename " ) {
2020-04-21 03:09:35 +00:00
// See also comments in test_from_raw_unnecessary_percent_decode.
2019-02-07 21:24:24 +00:00
DispositionParam ::Filename ( value )
} else {
DispositionParam ::Unknown ( param_name . to_owned ( ) , value )
} ;
cd . parameters . push ( param ) ;
}
}
Ok ( cd )
}
2021-12-01 19:42:02 +00:00
/// Returns `true` if type is [`Inline`](DispositionType::Inline).
2019-02-07 21:24:24 +00:00
pub fn is_inline ( & self ) -> bool {
2020-07-21 07:40:30 +00:00
matches! ( self . disposition , DispositionType ::Inline )
2019-02-07 21:24:24 +00:00
}
2021-12-01 19:42:02 +00:00
/// Returns `true` if type is [`Attachment`](DispositionType::Attachment).
2019-02-07 21:24:24 +00:00
pub fn is_attachment ( & self ) -> bool {
2020-07-21 07:40:30 +00:00
matches! ( self . disposition , DispositionType ::Attachment )
2019-02-07 21:24:24 +00:00
}
2021-12-01 19:42:02 +00:00
/// Returns `true` if type is [`FormData`](DispositionType::FormData).
2019-02-07 21:24:24 +00:00
pub fn is_form_data ( & self ) -> bool {
2020-07-21 07:40:30 +00:00
matches! ( self . disposition , DispositionType ::FormData )
2019-02-07 21:24:24 +00:00
}
2021-12-01 19:42:02 +00:00
/// Returns `true` if type is [`Ext`](DispositionType::Ext) and the `disp_type` matches.
2021-04-19 01:31:11 +00:00
pub fn is_ext ( & self , disp_type : impl AsRef < str > ) -> bool {
matches! (
self . disposition ,
DispositionType ::Ext ( ref t ) if t . eq_ignore_ascii_case ( disp_type . as_ref ( ) )
)
2019-02-07 21:24:24 +00:00
}
/// Return the value of *name* if exists.
pub fn get_name ( & self ) -> Option < & str > {
2021-06-26 14:33:43 +00:00
self . parameters . iter ( ) . find_map ( DispositionParam ::as_name )
2019-02-07 21:24:24 +00:00
}
/// Return the value of *filename* if exists.
pub fn get_filename ( & self ) -> Option < & str > {
self . parameters
. iter ( )
2021-06-26 14:33:43 +00:00
. find_map ( DispositionParam ::as_filename )
2019-02-07 21:24:24 +00:00
}
/// Return the value of *filename\** if exists.
pub fn get_filename_ext ( & self ) -> Option < & ExtendedValue > {
self . parameters
. iter ( )
2021-06-26 14:33:43 +00:00
. find_map ( DispositionParam ::as_filename_ext )
2019-02-07 21:24:24 +00:00
}
/// Return the value of the parameter which the `name` matches.
2021-04-19 01:31:11 +00:00
pub fn get_unknown ( & self , name : impl AsRef < str > ) -> Option < & str > {
2019-02-07 21:24:24 +00:00
let name = name . as_ref ( ) ;
2021-06-26 14:33:43 +00:00
self . parameters . iter ( ) . find_map ( | p | p . as_unknown ( name ) )
2019-02-07 21:24:24 +00:00
}
/// Return the value of the extended parameter which the `name` matches.
2021-04-19 01:31:11 +00:00
pub fn get_unknown_ext ( & self , name : impl AsRef < str > ) -> Option < & ExtendedValue > {
2019-02-07 21:24:24 +00:00
let name = name . as_ref ( ) ;
2021-06-26 14:33:43 +00:00
self . parameters . iter ( ) . find_map ( | p | p . as_unknown_ext ( name ) )
2019-02-07 21:24:24 +00:00
}
}
2021-12-13 16:08:08 +00:00
impl TryIntoHeaderValue for ContentDisposition {
2019-12-05 17:35:43 +00:00
type Error = header ::InvalidHeaderValue ;
2019-02-07 21:24:24 +00:00
2021-01-15 02:11:10 +00:00
fn try_into_value ( self ) -> Result < header ::HeaderValue , Self ::Error > {
2019-02-07 21:24:24 +00:00
let mut writer = Writer ::new ( ) ;
let _ = write! ( & mut writer , " {} " , self ) ;
2019-12-05 17:35:43 +00:00
header ::HeaderValue ::from_maybe_shared ( writer . take ( ) )
2019-02-07 21:24:24 +00:00
}
}
impl Header for ContentDisposition {
fn name ( ) -> header ::HeaderName {
header ::CONTENT_DISPOSITION
}
fn parse < T : crate ::HttpMessage > ( msg : & T ) -> Result < Self , crate ::error ::ParseError > {
2019-04-06 22:02:02 +00:00
if let Some ( h ) = msg . headers ( ) . get ( & Self ::name ( ) ) {
2021-08-13 17:49:58 +00:00
Self ::from_raw ( h )
2019-02-07 21:24:24 +00:00
} else {
Err ( crate ::error ::ParseError ::Header )
}
}
}
impl fmt ::Display for DispositionType {
2019-12-07 18:46:51 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2019-02-07 21:24:24 +00:00
match self {
DispositionType ::Inline = > write! ( f , " inline " ) ,
DispositionType ::Attachment = > write! ( f , " attachment " ) ,
DispositionType ::FormData = > write! ( f , " form-data " ) ,
DispositionType ::Ext ( ref s ) = > write! ( f , " {} " , s ) ,
}
}
}
impl fmt ::Display for DispositionParam {
2019-12-07 18:46:51 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2019-11-06 14:08:37 +00:00
// All ASCII control characters (0-30, 127) including horizontal tab, double quote, and
2019-02-07 21:24:24 +00:00
// backslash should be escaped in quoted-string (i.e. "foobar").
2021-12-01 19:42:02 +00:00
//
// Ref: RFC 6266 §4.1 -> RFC 2616 §3.6
//
2019-11-06 14:08:37 +00:00
// filename-parm = "filename" "=" value
// value = token | quoted-string
// quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
// qdtext = <any TEXT except <">>
// quoted-pair = "\" CHAR
// TEXT = <any OCTET except CTLs,
// but including LWS>
// LWS = [CRLF] 1*( SP | HT )
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character
// (octets 0 - 31) and DEL (127)>
//
2021-12-01 19:42:02 +00:00
// Ref: RFC 7578 S4.2 -> RFC 2183 S2 -> RFC 2045 S5.1
2019-11-06 14:08:37 +00:00
// parameter := attribute "=" value
// attribute := token
// ; Matching of attributes
// ; is ALWAYS case-insensitive.
// value := token / quoted-string
// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
// or tspecials>
// tspecials := "(" / ")" / "<" / ">" / "@" /
// "," / ";" / ":" / "\" / <">
// "/" / "[" / "]" / "?" / "="
// ; Must be in quoted-string,
// ; to use within parameter values
//
//
2020-04-21 03:09:35 +00:00
// See also comments in test_from_raw_unnecessary_percent_decode.
2021-11-17 17:43:24 +00:00
2021-04-16 19:28:21 +00:00
static RE : Lazy < Regex > =
Lazy ::new ( | | Regex ::new ( " [ \x00 - \x08 \x10 - \x1F \x7F \" \\ \\ ] " ) . unwrap ( ) ) ;
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
match self {
DispositionParam ::Name ( ref value ) = > write! ( f , " name={} " , value ) ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
DispositionParam ::Filename ( ref value ) = > {
write! ( f , " filename= \" {} \" " , RE . replace_all ( value , " \\ $0 " ) . as_ref ( ) )
}
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
DispositionParam ::Unknown ( ref name , ref value ) = > write! (
f ,
" {}= \" {} \" " ,
name ,
& RE . replace_all ( value , " \\ $0 " ) . as_ref ( )
) ,
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
DispositionParam ::FilenameExt ( ref ext_value ) = > {
write! ( f , " filename*={} " , ext_value )
}
2021-11-17 17:43:24 +00:00
2019-02-07 21:24:24 +00:00
DispositionParam ::UnknownExt ( ref name , ref ext_value ) = > {
write! ( f , " {}*={} " , name , ext_value )
}
}
}
}
impl fmt ::Display for ContentDisposition {
2019-12-07 18:46:51 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2019-02-07 21:24:24 +00:00
write! ( f , " {} " , self . disposition ) ? ;
self . parameters
. iter ( )
2020-12-09 11:22:19 +00:00
. try_for_each ( | param | write! ( f , " ; {} " , param ) )
2019-02-07 21:24:24 +00:00
}
}
#[ cfg(test) ]
mod tests {
use super ::{ ContentDisposition , DispositionParam , DispositionType } ;
2021-04-19 01:31:11 +00:00
use crate ::http ::header ::{ Charset , ExtendedValue , HeaderValue } ;
2021-04-19 02:29:38 +00:00
2019-02-07 21:24:24 +00:00
#[ test ]
fn test_from_raw_basic ( ) {
assert! ( ContentDisposition ::from_raw ( & HeaderValue ::from_static ( " " ) ) . is_err ( ) ) ;
2023-07-17 01:38:12 +00:00
let a =
HeaderValue ::from_static ( " form-data; dummy=3; name=upload; filename= \" sample.png \" " ) ;
2019-02-07 21:24:24 +00:00
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 ( " sample.png " . to_owned ( ) ) ,
] ,
} ;
assert_eq! ( a , b ) ;
let a = HeaderValue ::from_static ( " attachment; filename= \" image.jpg \" " ) ;
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let b = ContentDisposition {
disposition : DispositionType ::Attachment ,
parameters : vec ! [ DispositionParam ::Filename ( " image.jpg " . to_owned ( ) ) ] ,
} ;
assert_eq! ( a , b ) ;
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 b = ContentDisposition {
disposition : DispositionType ::Attachment ,
parameters : vec ! [ DispositionParam ::Unknown (
String ::from ( " creation-date " ) ,
" Wed, 12 Feb 1997 16:29:51 -0500 " . to_owned ( ) ,
) ] ,
} ;
assert_eq! ( a , b ) ;
}
#[ test ]
fn test_from_raw_extended ( ) {
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 ! [
2023-07-17 01:38:12 +00:00
0xc2 , 0xa3 , 0x20 , b 'a' , b 'n' , b 'd' , 0x20 , 0xe2 , 0x82 , 0xac , 0x20 , b 'r' , b 'a' ,
b 't' , b 'e' , b 's' ,
2019-02-07 21:24:24 +00:00
] ,
} ) ] ,
} ;
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 ! [
2023-07-17 01:38:12 +00:00
0xc2 , 0xa3 , 0x20 , b 'a' , b 'n' , b 'd' , 0x20 , 0xe2 , 0x82 , 0xac , 0x20 , b 'r' , b 'a' ,
b 't' , b 'e' , b 's' ,
2019-02-07 21:24:24 +00:00
] ,
} ) ] ,
} ;
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; " ,
2020-04-21 03:09:35 +00:00
// Actually, a trailing semicolon is not compliant. But it is fine to accept.
2019-02-07 21:24:24 +00:00
) ;
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 ( ) {
2021-04-16 19:28:21 +00:00
let a = ContentDisposition ::from_raw ( & HeaderValue ::from_static ( " attachment " ) ) . unwrap ( ) ;
2019-02-07 21:24:24 +00:00
let b = ContentDisposition {
disposition : DispositionType ::Attachment ,
parameters : vec ! [ ] ,
} ;
assert_eq! ( a , b ) ;
2021-04-16 19:28:21 +00:00
let a = ContentDisposition ::from_raw ( & HeaderValue ::from_static ( " inline ; " ) ) . unwrap ( ) ;
2019-02-07 21:24:24 +00:00
let b = ContentDisposition {
disposition : DispositionType ::Inline ,
parameters : vec ! [ ] ,
} ;
assert_eq! ( a , b ) ;
2023-07-17 01:38:12 +00:00
let a =
ContentDisposition ::from_raw ( & HeaderValue ::from_static ( " unknown-disp-param " ) ) . unwrap ( ) ;
2019-02-07 21:24:24 +00:00
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 ( ) {
2021-12-01 19:42:02 +00:00
/* RFC 7578 Section 4.2:
2019-02-07 21:24:24 +00:00
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 . )
* /
2023-07-17 01:38:12 +00:00
let a = HeaderValue ::from_str ( " form-data; name=upload; filename= \" 文件.webp \" " ) . unwrap ( ) ;
2019-02-07 21:24:24 +00:00
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 ) ;
2019-07-17 07:55:44 +00:00
let a = HeaderValue ::from_str (
" form-data; name=upload; filename= \" 余固知謇謇之為患兮,忍而不能舍也.pptx \" " ,
)
. unwrap ( ) ;
2019-02-07 21:24:24 +00:00
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let b = ContentDisposition {
disposition : DispositionType ::FormData ,
parameters : vec ! [
DispositionParam ::Name ( String ::from ( " upload " ) ) ,
2023-07-17 01:38:12 +00:00
DispositionParam ::Filename ( String ::from ( " 余固知謇謇之為患兮,忍而不能舍也.pptx " ) ) ,
2019-02-07 21:24:24 +00:00
] ,
} ;
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 ( ) {
2021-04-16 19:28:21 +00:00
let a = HeaderValue ::from_static ( " form-data; filename= \" A semicolon here;.pdf \" " ) ;
2019-02-07 21:24:24 +00:00
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 ]
2020-04-21 03:09:35 +00:00
fn test_from_raw_unnecessary_percent_decode ( ) {
2021-12-01 19:42:02 +00:00
// In fact, RFC 7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with
2019-11-06 14:08:37 +00:00
// non-ASCII characters MAY be percent-encoded.
2021-12-01 19:42:02 +00:00
// On the contrary, RFC 6266 or other RFCs related to Content-Disposition response header
2019-11-06 14:08:37 +00:00
// do not mention such percent-encoding.
// So, it appears to be undecidable whether to percent-decode or not without
// knowing the usage scenario (multipart/form-data v.s. HTTP response header) and
// inevitable to unnecessarily percent-decode filename with %XX in the former scenario.
// Fortunately, it seems that almost all mainstream browsers just send UTF-8 encoded file
// names in quoted-string format (tested on Edge, IE11, Chrome and Firefox) without
// percent-encoding. So we do not bother to attempt to percent-decode.
2019-02-07 21:24:24 +00:00
let a = HeaderValue ::from_static (
2019-11-06 14:08:37 +00:00
" form-data; name=photo; filename= \" %74%65%73%74%2e%70%6e%67 \" " ,
2019-02-07 21:24:24 +00:00
) ;
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 ) ;
2023-07-17 01:38:12 +00:00
let a = HeaderValue ::from_static ( " form-data; name=photo; filename= \" %74%65%73%74.png \" " ) ;
2019-02-07 21:24:24 +00:00
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_from_raw_param_value_missing ( ) {
let a = HeaderValue ::from_static ( " form-data; name=upload ; filename= " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
let a = HeaderValue ::from_static ( " attachment; dummy=; filename=invoice.pdf " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
let a = HeaderValue ::from_static ( " inline; filename= " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
2019-11-06 14:08:37 +00:00
let a = HeaderValue ::from_static ( " inline; filename= \" \" " ) ;
2019-12-05 17:35:43 +00:00
assert! ( ContentDisposition ::from_raw ( & a )
. expect ( " parse cd " )
. get_filename ( )
. expect ( " filename " )
. is_empty ( ) ) ;
2019-02-07 21:24:24 +00:00
}
#[ test ]
fn test_from_raw_param_name_missing ( ) {
let a = HeaderValue ::from_static ( " inline; = \" test.txt \" " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
let a = HeaderValue ::from_static ( " inline; =diary.odt " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
let a = HeaderValue ::from_static ( " inline; = " ) ;
assert! ( ContentDisposition ::from_raw ( & a ) . is_err ( ) ) ;
}
#[ test ]
fn test_display_extended ( ) {
2021-04-16 19:28:21 +00:00
let as_string = " attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates " ;
2019-02-07 21:24:24 +00:00
let a = HeaderValue ::from_static ( as_string ) ;
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! ( as_string , display_rendered ) ;
let a = HeaderValue ::from_static ( " attachment; filename=colourful.csv " ) ;
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let display_rendered = format! ( " {} " , a ) ;
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 ( ) ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! ( as_string , display_rendered ) ;
}
#[ test ]
fn test_display_space_tab ( ) {
let as_string = " form-data; name=upload; filename= \" Space here.png \" " ;
let a = HeaderValue ::from_static ( as_string ) ;
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! ( as_string , display_rendered ) ;
let a : ContentDisposition = ContentDisposition {
disposition : DispositionType ::Inline ,
parameters : vec ! [ DispositionParam ::Filename ( String ::from ( " Tab \t here.png " ) ) ] ,
} ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! ( " inline; filename= \" Tab \x09 here.png \" " , display_rendered ) ;
}
#[ test ]
fn test_display_control_characters ( ) {
/* let a = "attachment; filename=\"carriage\rreturn.png\"";
let a = HeaderValue ::from_static ( a ) ;
let a : ContentDisposition = ContentDisposition ::from_raw ( & a ) . unwrap ( ) ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! (
" attachment; filename= \" carriage \\ \r return.png \" " ,
display_rendered
) ; * /
// No way to create a HeaderValue containing a carriage return.
let a : ContentDisposition = ContentDisposition {
disposition : DispositionType ::Inline ,
parameters : vec ! [ DispositionParam ::Filename ( String ::from ( " bell \x07 .png " ) ) ] ,
} ;
let display_rendered = format! ( " {} " , a ) ;
assert_eq! ( " inline; filename= \" bell \\ \x07 .png \" " , 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 " ) ) ;
}
}