#![feature(field_init_shorthand)]
extern crate memchr;
use memchr::memchr;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct MediaType<'a> {
pub mimetype: &'a str,
pub params: &'a str,
}
impl<'a> MediaType<'a> {
pub fn new(s: &'a str) -> Result<Self, ()> {
let (mimetype, params) = match memchr(b';', s.as_bytes()) {
Some(idx) => {
let (l, r) = s.split_at(idx);
(l, &r[1..])
},
None => (s, &""[..]),
};
let mimetype = mimetype.trim();
if mimetype.is_empty() {
return Err(());
}
Ok(MediaType { mimetype, params })
}
pub fn parts(&self) -> Result<(&'a str, &'a str), ()> {
let mut parts = self.mimetype.split('/');
let main = parts.next().ok_or(())?;
let sub = parts.next().ok_or(())?;
if parts.next().is_none() {
Ok((main, sub))
} else {
Err(())
}
}
}
#[derive(Copy, Clone, Debug, Hash)]
pub struct MediaParams<'a>(&'a str);
impl<'a> MediaParams<'a> {
pub fn new(s: &'a str) -> Self {
MediaParams(s)
}
}
impl<'a> Iterator for MediaParams<'a> {
type Item = Result<(&'a str, ParamValue<'a>), ()>;
fn next(&mut self) -> Option<Self::Item> {
let (key, rest) = match memchr(b'=', self.0.as_bytes()) {
Some(idx) => self.0.split_at(idx),
None => return None,
};
let key = key.trim();
let (val, rest) = match ParamValue::new(&rest[1..]) {
Ok(v) => v,
Err(()) => return Some(Err(())),
};
self.0 = rest;
Some(Ok((key, val)))
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum ParamValue<'a> {
Unquoted(&'a str),
Quoted(&'a str),
}
impl<'a> ParamValue<'a> {
fn new(s: &'a str) -> Result<(Self, &'a str), ()> {
if s.is_empty() {
return Err(());
}
if s.starts_with('"') {
let len = find_end_quote(s.as_bytes()).ok_or(())?;
let (val, rest) = (&s[1..]).split_at(len);
let rest = &rest[1..];
let (leftover, rest) = match memchr(b';', rest.as_bytes()) {
Some(idx) => {
let (l, r) = rest.split_at(idx);
(l, &r[1..])
},
None => (rest, &""[..]),
};
if !leftover.trim().is_empty() {
return Err(());
}
Ok((ParamValue::Quoted(val), rest))
} else {
let (val, rest) = match memchr(b';', s.as_bytes()) {
Some(idx) => {
let (l, r) = s.split_at(idx);
(l, &r[1..])
},
None => (s, &""[..]),
};
let val = val.trim();
if val.is_empty() {
return Err(());
}
Ok((ParamValue::Unquoted(val), rest))
}
}
pub fn inner(&self) -> &'a str {
match *self {
ParamValue::Unquoted(s) => s,
ParamValue::Quoted(s) => s,
}
}
}
fn find_end_quote(s: &[u8]) -> Option<usize> {
debug_assert!(s[0] == b'"');
let start = match s.get(1..) {
Some(x) => x,
None => return None,
};
let mut cur = start;
let mut len = 0;
loop {
let idx = match memchr(b'"', cur) {
Some(idx) => idx,
None => return None,
};
len += idx;
let (text, rest) = cur.split_at(idx);
if text.is_empty() || escape_count(text) % 2 == 0 {
break;
}
len += 1;
cur = match rest.get(1..) {
Some(x) => x,
None => return None,
};
}
Some(len)
}
fn escape_count(s: &[u8]) -> usize {
s.iter().rev().take_while(|&&b| b == b'\\').fold(0, |s, _| s + 1)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_media_type() {
let m = MediaType::new("text/html;charset=utf-8").unwrap();
assert_eq!(m.mimetype, "text/html");
assert_eq!(m.params, "charset=utf-8");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "html");
let m = MediaType::new("text/*;charset=utf-8").unwrap();
assert_eq!(m.mimetype, "text/*");
assert_eq!(m.params, "charset=utf-8");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "*");
let m = MediaType::new("image/*").unwrap();
assert_eq!(m.mimetype, "image/*");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "image");
assert_eq!(sub, "*");
let m = MediaType::new("text/json").unwrap();
assert_eq!(m.mimetype, "text/json");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "json");
let m = MediaType::new("text/json ;").unwrap();
assert_eq!(m.mimetype, "text/json");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "json");
let m = MediaType::new("text/json ; ").unwrap();
assert_eq!(m.mimetype, "text/json");
assert_eq!(m.params, " ");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "json");
let m = MediaType::new("text/html; charset=\"utf-8\"").unwrap();
assert_eq!(m.mimetype, "text/html");
assert_eq!(m.params, " charset=\"utf-8\"");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "html");
let m = MediaType::new("\t\t text/html \t; charset=utf-8 \t\t").unwrap();
assert_eq!(m.mimetype, "text/html");
assert_eq!(m.params, " charset=utf-8 \t\t");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "html");
let m = MediaType::new(" text /\t* \t; charset=utf-8").unwrap();
assert_eq!(m.mimetype, "text /\t*");
assert_eq!(m.params, " charset=utf-8");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text ");
assert_eq!(sub, "\t*");
let m = MediaType::new("\t\t text/html \t; charset=utf-8 \t\t").unwrap();
assert_eq!(m.mimetype, "text/html");
assert_eq!(m.params, " charset=utf-8 \t\t");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "html");
let m = MediaType::new("text/hello space").unwrap();
assert_eq!(m.mimetype, "text/hello space");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "text");
assert_eq!(sub, "hello space");
let m = MediaType::new("image/").unwrap();
assert_eq!(m.mimetype, "image/");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "image");
assert_eq!(sub, "");
let m = MediaType::new("image/ ").unwrap();
assert_eq!(m.mimetype, "image/");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "image");
assert_eq!(sub, "");
let m = MediaType::new("/json").unwrap();
assert_eq!(m.mimetype, "/json");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "");
assert_eq!(sub, "json");
let m = MediaType::new(" /json").unwrap();
assert_eq!(m.mimetype, "/json");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "");
assert_eq!(sub, "json");
let m = MediaType::new("/").unwrap();
assert_eq!(m.mimetype, "/");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "");
assert_eq!(sub, "");
let m = MediaType::new("\t\t / ").unwrap();
assert_eq!(m.mimetype, "/");
assert_eq!(m.params, "");
let (main, sub) = m.parts().unwrap();
assert_eq!(main, "");
assert_eq!(sub, "");
assert!(MediaType::new("").is_err());
assert!(MediaType::new(" \t").is_err());
assert!(MediaType::new(" \t; charet=utf8").is_err());
let m = MediaType::new("text ; charset=utf8").unwrap();
assert_eq!(m.mimetype, "text");
assert_eq!(m.params, " charset=utf8");
assert!(m.parts().is_err());
}
#[test]
fn test_escape_count() {
assert_eq!(escape_count(br"\"), 1);
assert_eq!(escape_count(br"\\"), 2);
assert_eq!(escape_count(br"\\\"), 3);
assert_eq!(escape_count(br"\\\\"), 4);
assert_eq!(escape_count(br"42 \\\\"), 4);
assert_eq!(escape_count(br"\\ 42 \\\\"), 4);
assert_eq!(escape_count(br"\\ \\\\"), 4);
assert_eq!(escape_count(br"\\a\\\\"), 4);
}
#[test]
fn test_find_end_quote() {
assert_eq!(find_end_quote(b"\""), None);
assert_eq!(find_end_quote(b"\"\""), Some(b"".len()));
assert_eq!(find_end_quote(b"\"utf-8\""), Some(b"utf-8".len()));
assert_eq!(find_end_quote(b"\"'utf-8'\""), Some(b"'utf-8'".len()));
assert_eq!(find_end_quote(b"\"utf-8\"; key=value"), Some(b"utf-8".len()));
assert_eq!(find_end_quote(b"\"hello \\\"world\\\" 1337\""),
Some(b"hello \\\"world\\\" 1337".len()));
assert_eq!(find_end_quote(b"\"abcd fghi\\\" jklm \"; nopq"),
Some(b"abcd fghi\\\" jklm ".len()));
assert_eq!(find_end_quote(b"\"utf-8; key=value"), None);
assert_eq!(find_end_quote(b"\"utf-8\\\"; key=value"), None);
}
#[test]
fn test_param_value() {
assert_eq!(ParamValue::new(""), Err(()));
assert_eq!(ParamValue::new(" \t"), Err(()));
assert_eq!(ParamValue::new(" \t;charset=utf8"), Err(()));
assert_eq!(ParamValue::new("\""), Err(()));
assert_eq!(ParamValue::new("\"\""), Ok((
ParamValue::Quoted(&""[..]),
&""[..],
)));
assert_eq!(ParamValue::new("\"\""), Ok((
ParamValue::Quoted(&""[..]),
&""[..],
)));
assert_eq!(ParamValue::new("\"utf-8\""), Ok((
ParamValue::Quoted(&"utf-8"[..]),
&""[..],
)));
assert_eq!(ParamValue::new("\"utf-8, \\\" wat\"; key=value"), Ok((
ParamValue::Quoted(&"utf-8, \\\" wat"[..]),
&" key=value"[..],
)));
assert_eq!(ParamValue::new("\"utf-8; other\"; key=value"), Ok((
ParamValue::Quoted(&"utf-8; other"[..]),
&" key=value"[..],
)));
assert_eq!(ParamValue::new("\"utf-8\\\"\"; key=value"), Ok((
ParamValue::Quoted(&"utf-8\\\""[..]),
&" key=value"[..],
)));
assert_eq!(ParamValue::new("\"utf-8\\\"\" \t\t ; key=value"), Ok((
ParamValue::Quoted(&"utf-8\\\""[..]),
&" key=value"[..],
)));
assert_eq!(ParamValue::new("\"utf-8\\\"\" wrong; key=value"), Err(()));
assert_eq!(ParamValue::new("utf-8; key=value"), Ok((
ParamValue::Unquoted(&"utf-8"[..]),
&" key=value"[..],
)));
assert_eq!(ParamValue::new("some-value "), Ok((
ParamValue::Unquoted(&"some-value"[..]),
&""[..],
)));
assert_eq!(ParamValue::new("utf-8 abc; key=value"), Ok((
ParamValue::Unquoted(&"utf-8 abc"[..]),
&" key=value"[..],
)));
}
#[test]
fn test_media_params() {
let mut p = MediaParams::new("charset=utf-8");
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Unquoted("utf-8"));
assert!(p.next().is_none());
let mut p = MediaParams::new(" charset=\"utf-8\"");
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Quoted("utf-8"));
assert!(p.next().is_none());
let mut p = MediaParams::new(
" \tcharset=utf-8; chars=\"utf-42; wat\";key=\"some \\\"value\\\"\"; k=v \t\t"
);
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Unquoted("utf-8"));
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "chars");
assert_eq!(v, ParamValue::Quoted("utf-42; wat"));
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "key");
assert_eq!(v, ParamValue::Quoted("some \\\"value\\\""));
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "k");
assert_eq!(v, ParamValue::Unquoted("v"));
assert!(p.next().is_none());
}
#[test]
fn test_media_type_params() {
let m = MediaType::new("text/html;charset=utf-8").unwrap();
assert_eq!(m.mimetype, "text/html");
let mut p = MediaParams::new(m.params);
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Unquoted("utf-8"));
assert!(p.next().is_none());
let m = MediaType::new("text/*;charset=utf-8").unwrap();
assert_eq!(m.mimetype, "text/*");
let mut p = MediaParams::new(m.params);
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Unquoted("utf-8"));
assert!(p.next().is_none());
let m = MediaType::new("text/html; charset=\"utf-8\"").unwrap();
assert_eq!(m.mimetype, "text/html");
let mut p = MediaParams::new(m.params);
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Quoted("utf-8"));
assert!(p.next().is_none());
let m = MediaType::new("text/json; charset=\"utf-8\"; key=val \t").unwrap();
assert_eq!(m.mimetype, "text/json");
let mut p = MediaParams::new(m.params);
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "charset");
assert_eq!(v, ParamValue::Quoted("utf-8"));
let (k, v) = p.next().unwrap().unwrap();
assert_eq!(k, "key");
assert_eq!(v, ParamValue::Unquoted("val"));
assert!(p.next().is_none());
let m = MediaType::new("text/json").unwrap();
assert_eq!(m.mimetype, "text/json");
let mut p = MediaParams::new(m.params);
assert!(p.next().is_none());
let m = MediaType::new("text/json ;").unwrap();
assert_eq!(m.mimetype, "text/json");
let mut p = MediaParams::new(m.params);
assert!(p.next().is_none());
}
}