swindon 0.7.8

An HTTP edge (frontend) server with smart websockets support
use std::str::from_utf8;


#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentType {
    Absent,
    Valid,
    Invalid,
}

pub fn check_json<'x, I>(headers: I) -> ContentType
    where I: Iterator<Item=(&'x str, &'x [u8])>,
{
    use self::ContentType::*;
    let mut cur = Absent;
    for (name, value) in headers {
        if name.eq_ignore_ascii_case("Content-Type") {
            match cur {
                Absent => {
                    let mut chunks = value.split(|&x| x == b';');
                    let first = from_utf8(chunks.next().unwrap());
                    if first.is_err() {
                        info!("Invalid content type {:?}, must be json",
                            String::from_utf8_lossy(value));
                        return Invalid;
                    }
                    let mime = first.unwrap().trim();
                    if !mime.eq_ignore_ascii_case("application/json") {
                        info!("Invalid content type {:?}, must be json",
                            String::from_utf8_lossy(value));
                        return Invalid;
                    }
                    for chunk in chunks {
                        let mut pair = chunk.split(|&x| x == b'=');
                        match from_utf8(pair.next().unwrap()) {
                            Ok(s) if s.trim() == "charset" => {
                                let charset = pair.next()
                                    .and_then(|c| from_utf8(c).ok())
                                    .map(|c| c.trim());
                                match charset {
                                    Some("utf-8") | Some("utf8") => {}
                                    _ => {
                                        info!("Invalid content type {:?}, \
                                            charset must be utf-8",
                                            String::from_utf8_lossy(value));
                                        return Invalid;
                                    }
                                }
                            }
                            Ok(..) => {}
                            Err(..) => {}
                        }
                    }
                    cur = Valid;
                }
                Valid | Invalid => {
                    info!("Invalid content type, duplicate header");
                    return Invalid;
                }
            }
        }
    }
    return cur;
}

#[cfg(test)]
mod test {
    use super::check_json;
    use super::ContentType::*;

    #[test]
    fn simple() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json"[..])
        ].into_iter()), Valid);
    }
    #[test]
    fn charset() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; charset=utf-8"[..]),
        ].into_iter()), Valid);
    }

    #[test]
    fn bad_charset() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; charset=windows-1251"[..]),
        ].into_iter()), Invalid);
    }

    #[test]
    fn duplicate() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; charset=utf-8"[..]),
            ("Content-Type", &b"application/json; charset=utf-8"[..]),
        ].into_iter()), Invalid);
    }

    #[test]
    fn extra_kwargs() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; xx=yy; charset=utf-8"[..]),
        ].into_iter()), Valid);
    }

    #[test]
    fn extra_non_utf8() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; xx=\x99; charset=utf-8"[..]),
        ].into_iter()), Valid);
    }

    #[test]
    fn non_utf8_charset() {
        assert_eq!(check_json(vec![
            ("Content-Type", &b"application/json; charset=\x99"[..]),
        ].into_iter()), Invalid);
    }
}