Parse and test params in ContentType.

This commit is contained in:
Sergio Benitez 2016-12-15 08:49:10 -08:00
parent 254f03fa4e
commit 0cc379b82f
1 changed files with 71 additions and 23 deletions

View File

@ -144,11 +144,11 @@ impl ContentType {
"form", Form, is_form => "application", "x-www-form-urlencoded", "form", Form, is_form => "application", "x-www-form-urlencoded",
"data form", DataForm, is_data_form => "multipart", "form-data", "data form", DataForm, is_data_form => "multipart", "form-data",
"JSON", JSON, is_json => "application", "json", "JSON", JSON, is_json => "application", "json",
"XML", XML, is_xml => "text", "xml"; "charset=utf-8", "XML", XML, is_xml => "text", "xml" ; "charset=utf-8",
"HTML", HTML, is_html => "text", "html"; "charset=utf-8", "HTML", HTML, is_html => "text", "html" ; "charset=utf-8",
"Plain", Plain, is_plain => "text", "plain"; "charset=utf-8", "Plain", Plain, is_plain => "text", "plain" ; "charset=utf-8",
"JavaScript", JavaScript, is_javascript => "application", "javascript", "JavaScript", JavaScript, is_javascript => "application", "javascript",
"CSS", CSS, is_css => "text", "css"; "charset=utf-8", "CSS", CSS, is_css => "text", "css" ; "charset=utf-8",
"PNG", PNG, is_png => "image", "png", "PNG", PNG, is_png => "image", "png",
"GIF", GIF, is_gif => "image", "gif", "GIF", GIF, is_gif => "image", "gif",
"BMP", BMP, is_bmp => "image", "bmp", "BMP", BMP, is_bmp => "image", "bmp",
@ -204,6 +204,16 @@ fn is_valid_char(c: char) -> bool {
} }
} }
fn is_valid_token(string: &str) -> bool {
if string.len() < 1 {
return false;
}
string.chars().take(1).all(is_valid_first_char)
&& string.chars().skip(1).all(is_valid_char)
}
impl FromStr for ContentType { impl FromStr for ContentType {
type Err = &'static str; type Err = &'static str;
@ -250,8 +260,8 @@ impl FromStr for ContentType {
}; };
let top_s = &raw[..slash]; let top_s = &raw[..slash];
let (sub_s, _rest) = match raw.find(';') { let (sub_s, params) = match raw.find(';') {
Some(j) => (&raw[(slash + 1)..j], Some(&raw[(j + 1)..])), Some(j) => (raw[(slash + 1)..j].trim_right(), Some(raw[(j + 1)..].trim_left())),
None => (&raw[(slash + 1)..], None), None => (&raw[(slash + 1)..], None),
}; };
@ -259,20 +269,36 @@ impl FromStr for ContentType {
return Err("Empty string."); return Err("Empty string.");
} }
if !is_valid_first_char(top_s.chars().next().unwrap()) if !is_valid_token(top_s) || !is_valid_token(sub_s) {
|| !is_valid_first_char(sub_s.chars().next().unwrap()) { return Err("Invalid characters in type or subtype.");
return Err("Invalid first char.");
} }
if top_s.contains(|c| !is_valid_char(c)) let mut trimmed_params = vec![];
|| sub_s.contains(|c| !is_valid_char(c)) { for param in params.into_iter().flat_map(|p| p.split(";")) {
return Err("Invalid character in string."); let param = param.trim_left();
for (i, split) in param.split("=").enumerate() {
if split.trim() != split {
return Err("Whitespace not allowed around = character.");
} }
let (top_s, sub_s) = (top_s.to_lowercase(), sub_s.to_lowercase()); match i {
0 => if !is_valid_token(split) {
return Err("Invalid parameter name.");
},
1 => if !((split.starts_with('"') && split.ends_with('"'))
|| is_valid_token(split)) {
return Err("Invalid parameter value.");
},
_ => return Err("Malformed parameter.")
}
}
// FIXME: Use `rest` to find params. trimmed_params.push(param);
Ok(ContentType::new(top_s, sub_s)) }
let (ttype, subtype) = (top_s.to_lowercase(), sub_s.to_lowercase());
let params = params.map(|_| trimmed_params.join(";"));
Ok(ContentType::with_params(ttype, subtype, params))
} }
} }
@ -331,7 +357,10 @@ mod test {
macro_rules! assert_parse { macro_rules! assert_parse {
($string:expr) => ({ ($string:expr) => ({
let result = ContentType::from_str($string); let result = ContentType::from_str($string);
assert!(result.is_ok()); if let Err(e) = result {
println!("{:?} failed to parse: {}", $string, e);
}
result.unwrap() result.unwrap()
}); });
@ -339,6 +368,7 @@ mod test {
let c = assert_parse!($string); let c = assert_parse!($string);
assert_eq!(c.ttype, $ct.ttype); assert_eq!(c.ttype, $ct.ttype);
assert_eq!(c.subtype, $ct.subtype); assert_eq!(c.subtype, $ct.subtype);
assert_eq!(c.params, $ct.params);
c c
}) })
} }
@ -347,27 +377,35 @@ mod test {
fn test_simple() { fn test_simple() {
assert_parse!("application/json", ContentType::JSON); assert_parse!("application/json", ContentType::JSON);
assert_parse!("*/json", ContentType::new("*", "json")); assert_parse!("*/json", ContentType::new("*", "json"));
assert_parse!("text/html", ContentType::HTML); assert_parse!("text/html;charset=utf-8", ContentType::HTML);
assert_parse!("TEXT/html", ContentType::HTML); assert_parse!("text/html ; charset=utf-8", ContentType::HTML);
assert_parse!("text/html ;charset=utf-8", ContentType::HTML);
assert_parse!("TEXT/html;charset=utf-8", ContentType::HTML);
assert_parse!("*/*", ContentType::Any); assert_parse!("*/*", ContentType::Any);
assert_parse!("application/*", ContentType::new("application", "*")); assert_parse!("application/*", ContentType::new("application", "*"));
} }
#[test] #[test]
fn test_params() { fn test_params() {
// TODO: Test these. assert_parse!("*/*;a=1;b=2;c=3;d=4",
assert_parse!("application/json; charset=utf-8", ContentType::JSON); ContentType::with_params("*", "*", Some("a=1;b=2;c=3;d=4")));
assert_parse!("*/*;", ContentType::Any); assert_parse!("*/*; a=1; b=2; c=3;d=4",
ContentType::with_params("*", "*", Some("a=1;b=2;c=3;d=4")));
assert_parse!("application/*;else=1", assert_parse!("application/*;else=1",
ContentType::with_params("application", "*", Some("else=1"))); ContentType::with_params("application", "*", Some("else=1")));
assert_parse!("*/*;charset=utf8;else=1", assert_parse!("*/*;charset=utf-8;else=1",
ContentType::with_params("*", "*", Some("charset=utf-8;else=1"))); ContentType::with_params("*", "*", Some("charset=utf-8;else=1")));
assert_parse!("*/*; charset=utf-8; else=1",
ContentType::with_params("*", "*", Some("charset=utf-8;else=1")));
assert_parse!("*/*; charset=\"utf-8\"; else=1",
ContentType::with_params("*", "*", Some("charset=\"utf-8\";else=1")));
} }
#[test] #[test]
fn test_bad_parses() { fn test_bad_parses() {
assert_no_parse!("application//json"); assert_no_parse!("application//json");
assert_no_parse!("application///json"); assert_no_parse!("application///json");
assert_no_parse!("*&_/*)()");
assert_no_parse!("/json"); assert_no_parse!("/json");
assert_no_parse!("text/"); assert_no_parse!("text/");
assert_no_parse!("text//"); assert_no_parse!("text//");
@ -376,5 +414,15 @@ mod test {
assert_no_parse!("/*"); assert_no_parse!("/*");
assert_no_parse!("///"); assert_no_parse!("///");
assert_no_parse!(""); assert_no_parse!("");
assert_no_parse!("*/*;");
assert_no_parse!("*/*;a=");
assert_no_parse!("*/*;a= ");
assert_no_parse!("*/*;a=@#$%^&*()");
assert_no_parse!("*/*;;");
assert_no_parse!("*/*;=;");
assert_no_parse!("*/*=;");
assert_no_parse!("*/*=;=");
assert_no_parse!("*/*; a=b;");
assert_no_parse!("*/*; a = b");
} }
} }