use omnom::prelude::*;
use std::collections::HashMap;
use std::io::prelude::*;
use std::io::Cursor;
use super::Mime;
#[allow(dead_code)]
pub(crate) fn parse(s: &str) -> crate::Result<Mime> {
let mut s = Cursor::new(s);
let mut base_type = vec![];
let read = s.read_until(b'/', &mut base_type).unwrap();
if read == 0 || read == 1 {
crate::bail!("mime must be a type followed by a slash");
} else if let Some(b'/') = base_type.last() {
base_type.pop();
} else {
crate::bail!("mime must be a type followed by a slash");
}
validate_code_points(&base_type)?;
let mut sub_type = vec![];
let read = s.read_until(b';', &mut sub_type).unwrap();
if read == 0 {
crate::bail!("no subtype found");
}
if let Some(b';') = sub_type.last() {
sub_type.pop();
}
validate_code_points(&sub_type)?;
let basetype = String::from_utf8(base_type).unwrap();
let subtype = String::from_utf8(sub_type).unwrap();
let mut mime = Mime {
essence: format!("{}/{}", &basetype, &subtype),
basetype,
subtype,
parameters: None,
static_essence: None,
static_basetype: None,
static_subtype: None,
};
loop {
if s.fill_buf().unwrap().is_empty() {
break;
}
s.skip_while(is_http_whitespace_char)?;
let mut param_name = vec![];
s.read_while(&mut param_name, |b| b != b';' && b != b'=')?;
validate_code_points(¶m_name)?;
let mut param_name = String::from_utf8(param_name).unwrap();
param_name.make_ascii_lowercase();
let mut token = vec![0; 1];
s.read_exact(&mut token).unwrap();
if token[0] == b';' {
continue;
}
let mut param_value = vec![];
s.read_until(b';', &mut param_value).unwrap();
if let Some(b';') = param_value.last() {
param_value.pop();
}
validate_code_points(¶m_value)?;
let mut param_value = String::from_utf8(param_value).unwrap();
param_value.make_ascii_lowercase();
mime.parameters.get_or_insert_with(HashMap::new);
mime.parameters
.as_mut()
.unwrap()
.insert(param_name, param_value);
}
Ok(mime)
}
fn validate_code_points(buf: &[u8]) -> crate::Result<()> {
let all = buf.iter().all(|b| match b {
b'-' | b'!' | b'#' | b'$' | b'%' => true,
b'&' | b'\'' | b'*' | b'+' | b'.' => true,
b'^' | b'_' | b'`' | b'|' | b'~' => true,
b'A'..=b'Z' => true,
b'a'..=b'z' => true,
b'0'..=b'9' => true,
_ => false,
});
if all {
Ok(())
} else {
crate::bail!("invalid HTTP code points found in mime")
}
}
fn is_http_whitespace_char(b: u8) -> bool {
match b {
b' ' | b'\t' | b'\n' | b'\r' => true,
_ => false,
}
}
#[test]
fn test() {
let mime = parse("text/html").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
let mime = parse("text/html;").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
let mime = parse("text/html; charset=utf-8").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
let mime = parse("text/html; charset=utf-8;").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
assert!(parse("text").is_err());
assert!(parse("text/").is_err());
assert!(parse("t/").is_err());
assert!(parse("t/h").is_ok());
}