use std::io::Read;
use std::collections::HashMap;
use std::mem;
use http::{Header, Error};
enum State {
HeaderFieldFirstByte,
HeaderField,
HeaderFieldParsed,
HeaderValueFirstByte,
HeaderValue,
HeaderValueAlmostParsed,
HeadersAlmostParsed,
HeadersParsed,
}
pub fn parse(reader: &mut Read) -> Result<HashMap<Header, String>, Error> {
let mut bytes = reader.bytes().peekable();
let mut state = State::HeaderFieldFirstByte;
let mut headers: HashMap<Header, String> = HashMap::new();
let mut current_header_field = String::new();
let mut current_header_value = String::new();
loop {
match state {
State::HeaderFieldFirstByte => {
match bytes.next().unwrap() {
Ok(b'\r') => { state = State::HeadersAlmostParsed },
Ok(b' ') | Ok(b'\n') | Ok(b':') | Err(_) => return Err(Error::InvalidHeaderField),
Ok(byte) => {
current_header_field.push(byte as char);
state = State::HeaderField;
},
}
},
State::HeaderField => {
match bytes.next().unwrap() {
Ok(b' ') | Err(_) => return Err(Error::InvalidHeaderField),
Ok(b':') => { state = State::HeaderFieldParsed },
Ok(byte) => { current_header_field.push(byte as char) },
}
},
State::HeaderFieldParsed => {
match *bytes.peek().unwrap() {
Ok(b' ') => { let _ = bytes.next().unwrap(); },
Err(_) => return Err(Error::InvalidHeaderValue),
Ok(_) => { state = State::HeaderValueFirstByte },
}
},
State::HeaderValueFirstByte => {
match bytes.next().unwrap() {
Ok(b' ') | Ok(b':') | Ok(b'\r') | Ok(b'\n') | Err(_) => return Err(Error::InvalidHeaderValue),
Ok(byte) => {
current_header_value.push(byte as char);
state = State::HeaderValue;
}
}
},
State::HeaderValue => {
match bytes.next().unwrap() {
Ok(b'\r') => { state = State::HeaderValueAlmostParsed },
Ok(b':') | Ok(b'\n') | Err(_) => return Err(Error::InvalidHeaderValue),
Ok(byte) => { current_header_value.push(byte as char) },
}
},
State::HeaderValueAlmostParsed => {
match bytes.next().unwrap() {
Ok(b'\n') => {
let field = mem::replace(&mut current_header_field, String::new());
let value = mem::replace(&mut current_header_value, String::new());
headers.insert(Header::from(&*field), value);
state = State::HeaderFieldFirstByte;
},
_ => return Err(Error::InvalidHeaderValue),
}
},
State::HeadersAlmostParsed => {
match bytes.next().unwrap() {
Ok(b'\n') => { state = State::HeadersParsed },
_ => return Err(Error::InvalidHeaders),
}
}
State::HeadersParsed => return Ok(headers),
}
}
}
#[cfg(test)]
mod tests {
use super::parse;
use http::Error;
#[test]
fn test_no_header() {
let mut line: &[u8] = b"\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let headers = parsed.unwrap();
assert_eq!(0, headers.len());
}
#[test]
fn test_one_line_header() {
let mut line: &[u8] = b"Header-Field: value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let headers = parsed.unwrap();
assert_eq!(1, headers.len());
let header = headers.get(&"Header-Field".into());
assert_eq!(Some(&"value".to_string()), header);
}
#[test]
fn test_two_lines_header() {
let mut line: &[u8] = b"Header-Field: value\r\nOther-Field: other value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let headers = parsed.unwrap();
assert_eq!(2, headers.len());
let header = headers.get(&"Header-Field".into());
assert_eq!(Some(&"value".to_string()), header);
let header = headers.get(&"Other-Field".into());
assert_eq!(Some(&"other value".to_string()), header);
}
#[test]
fn test_header_field_starting_with_whitespace() {
let mut line: &[u8] = b" Header-Field: value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(Err(Error::InvalidHeaderField), parsed);
}
#[test]
fn test_header_field_starting_with_colon() {
let mut line: &[u8] = b": value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(Err(Error::InvalidHeaderField), parsed);
}
#[test]
fn test_header_field_containing_whitespace() {
let mut line: &[u8] = b"Header Field: value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(Err(Error::InvalidHeaderField), parsed);
}
#[test]
fn test_header_value_starting_with_whitespace() {
let mut line: &[u8] = b"Header-Field: value\r\n\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let headers = parsed.unwrap();
assert_eq!(Some(&"value".to_string()), headers.get(&"Header-Field".into()));
}
}