#![feature(field_init_shorthand)]
#![feature(conservative_impl_trait)]
extern crate memchr;
use memchr::memchr;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Error {
Partial,
Syntax,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct RequestLine<'a> {
pub method: &'a str,
pub target: &'a str,
pub version: &'a str,
}
impl<'a> RequestLine<'a> {
pub fn new(buf: &'a [u8]) -> Result<(Self, &'a [u8])> {
let start = skip_empty_lines(buf)?;
let (line, rest) = next_line(start)?;
let line = std::str::from_utf8(line).map_err(|_| Error::Syntax)?;
let mut chunks = line.split(' ');
let method = chunks.next().ok_or(Error::Syntax)?;
let target = chunks.next().ok_or(Error::Syntax)?;
let version = chunks.next().ok_or(Error::Syntax)?;
if chunks.next().is_some() {
return Err(Error::Syntax);
}
Ok((RequestLine { method, target, version }, rest))
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct Header<'a> {
pub name: &'a str,
pub val: &'a [u8],
}
pub struct Headers<'a>(&'a [u8]);
impl<'a> Headers<'a> {
pub fn new(s: &'a [u8]) -> Self {
Headers(s)
}
pub fn into_inner(self) -> &'a [u8] { self.0 }
}
impl<'a> Iterator for Headers<'a> {
type Item = Result<Header<'a>>;
fn next(&mut self) -> Option<Self::Item> {
let (line, rest) = match next_line(self.0) {
Ok(x) => x,
Err(e) => return Some(Err(e)),
};
self.0 = rest;
if line.is_empty() {
return None;
}
let (name, val) = match memchr(b':', line) {
Some(idx) => line.split_at(idx),
None => return Some(Err(Error::Syntax)),
};
let name = match std::str::from_utf8(name) {
Ok(s) => s.trim(),
Err(_) => return Some(Err(Error::Syntax)),
};
if name.is_empty() {
return Some(Err(Error::Syntax));
}
let val = &val[1..];
Some(Ok(Header { name, val }))
}
}
fn skip_empty_lines<'a>(mut bytes: &'a [u8]) -> Result<&'a [u8]> {
loop {
match check_crlf(bytes) {
Ok(rest) => bytes = rest,
Err(Error::Partial) => return Err(Error::Partial),
Err(Error::Syntax) => return Ok(bytes),
}
}
}
fn next_line<'a>(bytes: &'a [u8]) -> Result<(&'a [u8], &'a [u8])> {
let (line, rest) = match memchr(b'\r', bytes) {
Some(idx) => bytes.split_at(idx),
None => return Err(Error::Partial),
};
let rest = check_crlf(rest)?;
Ok((line, rest))
}
fn check_crlf<'a>(bytes: &'a [u8]) -> Result<&'a [u8]> {
if bytes.len() < 2 {
Err(Error::Partial)
} else if bytes.starts_with(&b"\r\n"[..]) {
Ok(&bytes[2..])
} else {
Err(Error::Syntax)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_request_line() {
let (req, rest) = RequestLine::new(b"GET / HTTP/1.1\r\n\r\n").unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"\r\n"[..]);
let (req, rest) = RequestLine::new(
b"GET / HTTP/1.1\r\nHost: foo.com\r\nCookie: \r\n\r\n"
).unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"Host: foo.com\r\nCookie: \r\n\r\n"[..]);
let (req, rest) = RequestLine::new(
b"GET / HTTP/1.1\r\nA: A\r\nB: B\r\nC: C\r\nD: D\r\n\r\n"
).unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"A: A\r\nB: B\r\nC: C\r\nD: D\r\n\r\n"[..]);
let (req, rest) = RequestLine::new(
b"GET / HTTP/1.1\r\nHost: foo.com\r\nUser-Agent: \xe3\x81\xb2\xe3/1.0\r\n\r\n",
).unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"Host: foo.com\r\nUser-Agent: \xe3\x81\xb2\xe3/1.0\r\n\r\n"[..]);
let (req, rest) = RequestLine::new(b"GET / HTTP/1.1\r\n\r").unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"\r"[..]);
assert_eq!(RequestLine::new(&b"GET / HTTP/1.1\nHost: foo.bar\n\n"[..]),
Err(Error::Partial));
let (req, rest) = RequestLine::new(&b"\r\n\r\nGET / HTTP/1.1\r\n\r\n"[..]).unwrap();
assert_eq!(req.method, "GET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"\r\n"[..]);
assert_eq!(RequestLine::new(&b"\n\nGET / HTTP/1.1\n\n"[..]), Err(Error::Partial));
assert_eq!(RequestLine::new(&b"GET\n/ HTTP/1.1\r\nHost: foo.bar\r\n\r\n"[..]),
Err(Error::Syntax));
let (req, rest) = RequestLine::new(b"\n\n\nGET / HTTP/1.1\r\n\n").unwrap();
assert_eq!(req.method, "\n\n\nGET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "HTTP/1.1");
assert_eq!(rest, &b"\n"[..]);
let (req, rest) = RequestLine::new(b"\r\n\nGET / H\tTTP/1.1\r\n\n").unwrap();
assert_eq!(req.method, "\nGET");
assert_eq!(req.target, "/");
assert_eq!(req.version, "H\tTTP/1.1");
assert_eq!(rest, &b"\n"[..]);
assert_eq!(RequestLine::new(b"\r\n\rGET / HTTP/1.1\r\n\n"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET /some path/ HTTP/1.1\r\n\r"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET\r\n"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET /\r\n"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET / HTTP/1.1 \r\n"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET / \r\n"), Err(Error::Syntax));
assert_eq!(RequestLine::new(b"GET / HTTP/1.1"), Err(Error::Partial));
assert_eq!(RequestLine::new(b"GET / HTTP/1.1\r"), Err(Error::Partial));
assert_eq!(RequestLine::new(b"GET / HTTP/1.1\n"), Err(Error::Partial));
}
#[test]
fn test_headers() {
let mut h = Headers::new(
b"Content-Type: text/html\r\nContent-Length: 1337\r\n\r\nbody text"
);
let n = h.next().unwrap().unwrap();
assert_eq!(n.name, "Content-Type");
assert_eq!(n.val, b" text/html");
let n = h.next().unwrap().unwrap();
assert_eq!(n.name, "Content-Length");
assert_eq!(n.val, b" 1337");
assert!(h.next().is_none());
assert_eq!(h.into_inner(), b"body text");
let mut h = Headers::new(
b" Content-Type \t\t: text/html\r\n\r\nbody text"
);
let n = h.next().unwrap().unwrap();
assert_eq!(n.name, "Content-Type");
assert_eq!(n.val, b" text/html");
assert!(h.next().is_none());
assert_eq!(h.into_inner(), b"body text");
let mut h = Headers::new(
b"\tContent-Type \t\t: text/html\r\n\r\n"
);
let n = h.next().unwrap().unwrap();
assert_eq!(n.name, "Content-Type");
assert_eq!(n.val, b" text/html");
assert!(h.next().is_none());
assert_eq!(h.into_inner(), b"");
let mut h = Headers::new(
b" Content-Type \t\t: text/html\n"
);
let n = h.next().unwrap();
assert_eq!(n, Err(Error::Partial));
}
#[test]
fn test_skip_empty_lines() {
assert_eq!(skip_empty_lines(b"GET"), Ok(&b"GET"[..]));
assert_eq!(skip_empty_lines(b"\r\n\r\nGET"), Ok(&b"GET"[..]));
assert_eq!(skip_empty_lines(b"\r\n\rGET"), Ok(&b"\rGET"[..]));
assert_eq!(skip_empty_lines(b"\nGET"), Ok(&b"\nGET"[..]));
}
#[test]
fn test_next_line() {
assert_eq!(next_line(b"abc\r\ndef"), Ok((&b"abc"[..], &b"def"[..])));
assert_eq!(next_line(b"abc def\r\nghi"), Ok((&b"abc def"[..], &b"ghi"[..])));
assert_eq!(next_line(b"abc\r\n"), Ok((&b"abc"[..], &b""[..])));
assert_eq!(next_line(b"abc"), Err(Error::Partial));
assert_eq!(next_line(b"abc\n"), Err(Error::Partial));
assert_eq!(next_line(b"\r\ndef"), Ok((&b""[..], &b"def"[..])));
assert_eq!(next_line(b""), Err(Error::Partial));
}
#[test]
fn test_check_crlf() {
assert_eq!(check_crlf(b"\r\nabc"), Ok(&b"abc"[..]));
assert_eq!(check_crlf(b"\r"), Err(Error::Partial));
assert_eq!(check_crlf(b""), Err(Error::Partial));
assert_eq!(check_crlf(b"\n"), Err(Error::Partial));
assert_eq!(check_crlf(b"\nabc"), Err(Error::Syntax));
assert_eq!(check_crlf(b"abc\r\n"), Err(Error::Syntax));
}
}