use std::io::Read;
use http::{Version, Method, Error};
enum State {
MethodFirstByte,
MethodMaybeConnect,
MethodMaybeDelete,
MethodMaybeGet,
MethodMaybeHead,
MethodMaybeOptions,
MethodMaybePatchOrPostOrPut,
MethodMaybePatch,
MethodMaybePost,
MethodMaybePut,
MethodMaybeTrace,
MethodParsed,
PathFirstByte,
Path,
PathParsed,
VersionFirstByte,
Version,
VersionOmitted,
VersionParsed,
RequestLineParsed,
}
pub fn parse(reader: &mut Read) -> Result<(Method, String, Version), Error> {
let mut method: Method = Method::Get;
let mut path: String = String::new();
let mut version: Version = Version::Http11;
let mut bytes = reader.bytes().peekable();
let mut state = State::MethodFirstByte;
loop {
match state {
State::MethodFirstByte => {
match bytes.next().unwrap() {
Ok(b'C') => { state = State::MethodMaybeConnect },
Ok(b'D') => { state = State::MethodMaybeDelete },
Ok(b'G') => { state = State::MethodMaybeGet },
Ok(b'H') => { state = State::MethodMaybeHead },
Ok(b'O') => { state = State::MethodMaybeOptions },
Ok(b'P') => { state = State::MethodMaybePatchOrPostOrPut },
Ok(b'T') => { state = State::MethodMaybeTrace },
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeConnect => {
let rest: Vec<u8> = (&mut bytes).take(6).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"ONNECT" => {
method = Method::Connect;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeDelete => {
let rest: Vec<u8> = (&mut bytes).take(5).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"ELETE" => {
method = Method::Delete;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeGet => {
let rest: Vec<u8> = (&mut bytes).take(2).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"ET" => {
method = Method::Get;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeHead => {
let rest: Vec<u8> = (&mut bytes).take(3).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"EAD" => {
method = Method::Head;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeOptions => {
let rest: Vec<u8> = (&mut bytes).take(6).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"PTIONS" => {
method = Method::Options;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybePatchOrPostOrPut => {
match bytes.next().unwrap() {
Ok(b'A') => { state = State::MethodMaybePatch },
Ok(b'O') => { state = State::MethodMaybePost },
Ok(b'U') => { state = State::MethodMaybePut },
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybePatch => {
let rest: Vec<u8> = (&mut bytes).take(3).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"TCH" => {
method = Method::Patch;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybePost => {
let rest: Vec<u8> = (&mut bytes).take(2).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"ST" => {
method = Method::Post;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybePut => {
let rest: Vec<u8> = (&mut bytes).take(1).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"T" => {
method = Method::Put;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodMaybeTrace => {
let rest: Vec<u8> = (&mut bytes).take(4).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"RACE" => {
method = Method::Trace;
state = State::MethodParsed;
},
_ => return Err(Error::InvalidMethod),
}
},
State::MethodParsed => {
match bytes.next().unwrap() {
Ok(b' ') => { state = State::PathFirstByte },
_ => return Err(Error::InvalidMethod),
}
},
State::PathFirstByte => {
match bytes.next().unwrap() {
Ok(b' ') | Ok(b'\r') | Ok(b'\n') | Err(_) => return Err(Error::InvalidPath),
Ok(byte) => {
path.push(byte as char);
state = State::Path
},
}
},
State::Path => {
match *bytes.peek().unwrap() {
Ok(b' ') | Ok(b'\r') | Ok(b'\n') => { state = State::PathParsed },
Ok(_) => { path.push(bytes.next().unwrap().unwrap() as char) },
Err(_) => return Err(Error::InvalidPath),
}
},
State::PathParsed => {
match bytes.next().unwrap() {
Ok(b' ') => { state = State::VersionFirstByte },
Ok(b'\r') => { state = State::VersionOmitted },
_ => return Err(Error::InvalidVersion),
}
},
State::VersionFirstByte => {
match bytes.next().unwrap() {
Ok(b'H') => { state = State::Version },
_ => return Err(Error::InvalidVersion),
}
},
State::Version => {
let rest: Vec<u8> = (&mut bytes).take(7).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"TTP/1.0" => {
version = Version::Http10;
},
b"TTP/1.1" => {
version = Version::Http11;
},
b"TTP/2.0" => {
version = Version::Http20;
},
_ => return Err(Error::InvalidVersion),
}
state = State::VersionParsed;
},
State::VersionOmitted => {
match bytes.next().unwrap() {
Ok(b'\n') => {
version = Version::Http09;
state = State::RequestLineParsed;
},
_ => return Err(Error::InvalidVersion),
}
},
State::VersionParsed => {
let rest: Vec<u8> = (&mut bytes).take(2).map( |b| b.unwrap() ).collect();
match rest.as_slice() {
b"\r\n" => { state = State::RequestLineParsed },
_ => return Err(Error::InvalidVersion),
}
},
State::RequestLineParsed => return Ok((method, path, version)),
}
}
}
#[cfg(test)]
mod tests {
use super::parse;
use http::{Method, Version, Error};
#[test]
fn test_get_http09() {
let mut line: &[u8] = b"GET /something\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Get, method);
assert_eq!("/something", path);
assert_eq!(Version::Http09, version);
}
#[test]
fn test_get_http10() {
let mut line: &[u8] = b"GET /something HTTP/1.0\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Get, method);
assert_eq!("/something", path);
assert_eq!(Version::Http10, version);
}
#[test]
fn test_post_http11() {
let mut line: &[u8] = b"POST /something HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Post, method);
assert_eq!("/something", path);
assert_eq!(Version::Http11, version);
}
#[test]
fn test_put_http10() {
let mut line: &[u8] = b"PUT / HTTP/1.0\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Put, method);
assert_eq!("/", path);
assert_eq!(Version::Http10, version);
}
#[test]
fn test_patch_http11() {
let mut line: &[u8] = b"PATCH /something HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, _, _) = parsed.unwrap();
assert_eq!(Method::Patch, method);
}
#[test]
fn test_delete_http11() {
let mut line: &[u8] = b"DELETE /something HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, _, _) = parsed.unwrap();
assert_eq!(Method::Delete, method);
}
#[test]
fn test_options_http11() {
let mut line: &[u8] = b"OPTIONS * HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Options, method);
assert_eq!("*", path);
assert_eq!(Version::Http11, version);
}
#[test]
fn test_connect_http11() {
let mut line: &[u8] = b"CONNECT example.com:1234 HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, path, version) = parsed.unwrap();
assert_eq!(Method::Connect, method);
assert_eq!("example.com:1234", path);
assert_eq!(Version::Http11, version);
}
#[test]
fn test_trace_http11() {
let mut line: &[u8] = b"TRACE / HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_ok());
let (method, _, _) = parsed.unwrap();
assert_eq!(Method::Trace, method);
}
#[test]
fn test_wrong_method_on_first_char() {
let mut line: &[u8] = b"FOOBAR / HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidMethod), parsed);
}
#[test]
fn test_wrong_method_on_fifth_char() {
let mut line: &[u8] = b"POSTME / HTTP/1.1\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidMethod), parsed);
}
#[test]
fn test_missing_path() {
let mut line: &[u8] = b"GET \r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidPath), parsed);
}
#[test]
fn test_missing_path_extra_whitespace() {
let mut line: &[u8] = b"GET \r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidPath), parsed);
}
#[test]
#[should_panic]
fn test_broken_end_of_line() {
let mut line: &[u8] = b"GET /\r";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidVersion), parsed);
}
#[test]
fn test_wrong_version() {
let mut line: &[u8] = b"GET / HTTP/1234\r\n";
let parsed = parse(&mut line);
assert_eq!(true, parsed.is_err());
assert_eq!(Err(Error::InvalidVersion), parsed);
}
}