Use eq_ignore_ascii_case to avoid mem allocation and improve Content-Dispostion display in content_disposition.rs

This commit is contained in:
Hung-I Wang 2018-08-13 21:08:32 +08:00
parent 1d86e9c8bf
commit f036c275e4
1 changed files with 79 additions and 31 deletions

View File

@ -9,6 +9,7 @@
use header; use header;
use header::ExtendedValue; use header::ExtendedValue;
use header::{Header, IntoHeaderValue, Writer}; use header::{Header, IntoHeaderValue, Writer};
use regex::Regex;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
@ -47,11 +48,14 @@ pub enum DispositionType {
impl<'a> From<&'a str> for DispositionType { impl<'a> From<&'a str> for DispositionType {
fn from(origin: &'a str) -> DispositionType { fn from(origin: &'a str) -> DispositionType {
match origin.as_bytes().to_ascii_lowercase().as_slice() { if origin.eq_ignore_ascii_case("inline") {
b"inline" => DispositionType::Inline, DispositionType::Inline
b"attachment" => DispositionType::Attachment, } else if origin.eq_ignore_ascii_case("attachment") {
b"form-data" => DispositionType::FormData, DispositionType::Attachment
_ => DispositionType::Ext(origin.to_owned()), } else if origin.eq_ignore_ascii_case("form-data") {
DispositionType::FormData
} else {
DispositionType::Ext(origin.to_owned())
} }
} }
} }
@ -292,14 +296,13 @@ impl ContentDisposition {
let (ext_value, new_left) = split_once_and_trim(left, ';'); let (ext_value, new_left) = split_once_and_trim(left, ';');
left = new_left; left = new_left;
let ext_value = header::parse_extended_value(ext_value)?; let ext_value = header::parse_extended_value(ext_value)?;
cd.parameters
.push(match param_name.to_ascii_lowercase().as_str() { let param = if param_name.eq_ignore_ascii_case("filename") {
"filename" => DispositionParam::FilenameExt(ext_value), DispositionParam::FilenameExt(ext_value)
_ => DispositionParam::UnknownExt( } else {
param_name.to_owned(), DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
ext_value, };
), cd.parameters.push(param);
});
} else { } else {
// regular parameters // regular parameters
let value = if left.starts_with('\"') { let value = if left.starts_with('\"') {
@ -313,10 +316,12 @@ impl ContentDisposition {
escaping = false; escaping = false;
quoted_string.push(c); quoted_string.push(c);
} else { } else {
if c == 0x5c // backslash if c == 0x5c
// backslash
{ {
escaping = true; escaping = true;
} else if c == 0x22 // double quote } else if c == 0x22
// double quote
{ {
end = Some(i + 1); // cuz skipped 1 for the leading quote end = Some(i + 1); // cuz skipped 1 for the leading quote
break; break;
@ -340,12 +345,15 @@ impl ContentDisposition {
if value.len() == 0 { if value.len() == 0 {
return Err(::error::ParseError::Header); return Err(::error::ParseError::Header);
} }
cd.parameters
.push(match param_name.to_ascii_lowercase().as_str() { let param = if param_name.eq_ignore_ascii_case("name") {
"name" => DispositionParam::Name(value), DispositionParam::Name(value)
"filename" => DispositionParam::Filename(value), } else if param_name.eq_ignore_ascii_case("filename") {
_ => DispositionParam::Unknown(param_name.to_owned(), value), DispositionParam::Filename(value)
}); } else {
DispositionParam::Unknown(param_name.to_owned(), value)
};
cd.parameters.push(param);
} }
} }
@ -467,17 +475,23 @@ impl fmt::Display for DispositionType {
impl fmt::Display for DispositionParam { impl fmt::Display for DispositionParam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO: More special charaters in filename should be escaped. // All ASCII control charaters (0-30, 127) excepting horizental tab, double quote, and
// backslash should be escaped in quoted-string (i.e. "foobar").
// Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... .
lazy_static! {
static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap();
}
match self { match self {
DispositionParam::Name(ref value) => { DispositionParam::Name(ref value) => write!(f, "name={}", value),
write!(f, "name={}", &value.replace('\"', "\\\""))
}
DispositionParam::Filename(ref value) => { DispositionParam::Filename(ref value) => {
write!(f, "filename=\"{}\"", &value.replace('\"', "\\\"")) write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
}
DispositionParam::Unknown(ref name, ref value) => {
write!(f, "{}=\"{}\"", name, &value.replace('\"', "\\\""))
} }
DispositionParam::Unknown(ref name, ref value) => write!(
f,
"{}=\"{}\"",
name,
&RE.replace_all(value, "\\$0").as_ref()
),
DispositionParam::FilenameExt(ref ext_value) => { DispositionParam::FilenameExt(ref ext_value) => {
write!(f, "filename*={}", ext_value) write!(f, "filename*={}", ext_value)
} }
@ -814,7 +828,6 @@ mod tests {
assert!(ContentDisposition::from_raw(&a).is_err()); assert!(ContentDisposition::from_raw(&a).is_err());
} }
#[test] #[test]
fn test_display_extended() { fn test_display_extended() {
let as_string = let as_string =
@ -841,11 +854,46 @@ mod tests {
.unwrap(); // ensure `\"` is there .unwrap(); // ensure `\"` is there
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();
println!("\n\n\n{:?}", a);
let display_rendered = format!("{}", a); let display_rendered = format!("{}", a);
assert_eq!(as_string, display_rendered); 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\there.png"))],
};
let display_rendered = format!("{}", a);
assert_eq!("inline; filename=\"Tab\x09here.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\\\rreturn.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] #[test]
fn test_param_methods() { fn test_param_methods() {
let param = DispositionParam::Filename(String::from("sample.txt")); let param = DispositionParam::Filename(String::from("sample.txt"));