use std::ascii::AsciiExt;
use std::iter::Enumerate;
use std::str::Bytes;
use super::{Mime, Source, Params, Indexed, CHARSET, UTF_8};
#[derive(Debug)]
pub enum ParseError {
MissingSlash,
MissingEqual,
MissingQuote,
InvalidToken {
pos: usize,
byte: u8,
},
}
pub fn parse(s: &str) -> Result<Mime, ParseError> {
if s == "*/*" {
return Ok(::STAR_STAR);
}
let mut iter = s.bytes().enumerate();
let mut start;
let slash;
loop {
match iter.next() {
Some((_, c)) if is_token(c) => (),
Some((i, b'/')) if i > 0 => {
slash = i;
start = i + 1;
break;
},
None => return Err(ParseError::MissingSlash), Some((pos, byte)) => return Err(ParseError::InvalidToken {
pos: pos,
byte: byte,
})
};
}
let mut plus = None;
loop {
match iter.next() {
Some((i, b'+')) if i > start => {
plus = Some(i);
},
Some((i, b';')) if i > start => {
start = i;
break;
},
Some((_, c)) if is_token(c) => (),
None => {
return Ok(Mime {
source: Source::Dynamic(s.to_ascii_lowercase()),
slash: slash,
plus: plus,
params: Params::None,
});
},
Some((pos, byte)) => return Err(ParseError::InvalidToken {
pos: pos,
byte: byte,
})
};
}
let params = try!(params_from_str(s, &mut iter, start));
let src = match params {
Params::Utf8(_) |
Params::None => s.to_ascii_lowercase(),
Params::Custom(semicolon, ref indices) => lower_ascii_with_params(s, semicolon, indices),
};
Ok(Mime {
source: Source::Dynamic(src),
slash: slash,
plus: plus,
params: params,
})
}
fn params_from_str(s: &str, iter: &mut Enumerate<Bytes>, mut start: usize) -> Result<Params, ParseError> {
let semicolon = start;
start += 1;
let mut params = Params::None;
'params: while start < s.len() {
let name;
'name: loop {
match iter.next() {
Some((i, b' ')) if i == start => start = i + 1,
Some((_, c)) if is_token(c) => (),
Some((i, b'=')) if i > start => {
name = Indexed(start, i);
start = i + 1;
break 'name;
},
None => return Err(ParseError::MissingEqual),
Some((pos, byte)) => return Err(ParseError::InvalidToken {
pos: pos,
byte: byte,
}),
}
}
let value;
let mut is_quoted = false;
'value: loop {
if is_quoted {
match iter.next() {
Some((i, b'"')) if i > start => {
value = Indexed(start, i);
start = i + 1;
break 'value;
},
Some((_, c)) if is_restricted_quoted_char(c) => (),
None => return Err(ParseError::MissingQuote),
Some((pos, byte)) => return Err(ParseError::InvalidToken {
pos: pos,
byte: byte,
}),
}
} else {
match iter.next() {
Some((i, b'"')) if i == start => {
is_quoted = true;
start = i + 1;
},
Some((_, c)) if is_token(c) => (),
Some((i, b';')) if i > start => {
value = Indexed(start, i);
start = i + 1;
break 'value;
}
None => {
value = Indexed(start, s.len());
start = s.len();
break 'value;
},
Some((pos, byte)) => return Err(ParseError::InvalidToken {
pos: pos,
byte: byte,
}),
}
}
}
match params {
Params::Utf8(i) => {
let i = i + 2;
let charset = Indexed(i, "charset".len() + i);
let utf8 = Indexed(charset.1 + 1, charset.1 + "utf-8".len() + 1);
params = Params::Custom(semicolon, vec![
(charset, utf8),
(name, value),
]);
},
Params::Custom(_, ref mut vec) => {
vec.push((name, value));
},
Params::None => {
if semicolon + 2 == name.0 && CHARSET == &s[name.0..name.1] {
if UTF_8 == &s[value.0..value.1] {
params = Params::Utf8(semicolon);
continue 'params;
}
}
params = Params::Custom(semicolon, vec![(name, value)]);
},
}
}
Ok(params)
}
fn lower_ascii_with_params(s: &str, semi: usize, params: &[(Indexed, Indexed)]) -> String {
let mut owned = s.to_owned();
owned[..semi].make_ascii_lowercase();
for &(ref name, ref value) in params {
owned[name.0..name.1].make_ascii_lowercase();
if &owned[name.0..name.1] == CHARSET.source {
owned[value.0..value.1].make_ascii_lowercase();
}
}
owned
}
macro_rules! byte_map {
($($flag:expr,)*) => ([
$($flag != 0,)*
])
}
static TOKEN_MAP: [bool; 256] = byte_map![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
fn is_token(c: u8) -> bool {
TOKEN_MAP[c as usize]
}
fn is_restricted_quoted_char(c: u8) -> bool {
c > 31 && c != 127
}
#[test]
fn test_lookup_tables() {
for (i, &valid) in TOKEN_MAP.iter().enumerate() {
let i = i as u8;
let should = match i {
b'a'...b'z' |
b'A'...b'Z' |
b'0'...b'9' |
b'!' |
b'#' |
b'$' |
b'%' |
b'&' |
b'\'' |
b'*' |
b'+' |
b'-' |
b'.' |
b'^' |
b'_' |
b'`' |
b'|' |
b'~' => true,
_ => false
};
assert_eq!(valid, should, "{:?} ({}) should be {}", i as char, i, should);
}
}