use std::borrow::{Cow, IntoCow, ToOwned};
use std::cmp::min;
use std::io::{self, Read, Write, BufRead};
use httparse;
use header::Headers;
use method::Method;
use uri::RequestUri;
use version::HttpVersion::{self, Http10, Http11};
use HttpError:: HttpTooLargeError;
use HttpResult;
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
pub enum HttpReader<R> {
SizedReader(R, u64),
ChunkedReader(R, Option<u64>),
EofReader(R),
EmptyReader(R),
}
impl<R: Read> HttpReader<R> {
pub fn into_inner(self) -> R {
match self {
SizedReader(r, _) => r,
ChunkedReader(r, _) => r,
EofReader(r) => r,
EmptyReader(r) => r,
}
}
}
impl<R: Read> Read for HttpReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
SizedReader(ref mut body, ref mut remaining) => {
debug!("Sized read, remaining={:?}", remaining);
if *remaining == 0 {
Ok(0)
} else {
let num = try!(body.read(buf)) as u64;
if num > *remaining {
*remaining = 0;
} else {
*remaining -= num;
}
Ok(num as usize)
}
},
ChunkedReader(ref mut body, ref mut opt_remaining) => {
let mut rem = match *opt_remaining {
Some(ref rem) => *rem,
None => try!(read_chunk_size(body))
};
debug!("Chunked read, remaining={:?}", rem);
if rem == 0 {
*opt_remaining = Some(0);
debug!("end of chunked");
return Ok(0)
}
let to_read = min(rem as usize, buf.len());
let count = try!(body.read(&mut buf[..to_read])) as u64;
rem -= count;
*opt_remaining = if rem > 0 {
Some(rem)
} else {
try!(eat(body, LINE_ENDING.as_bytes()));
None
};
Ok(count as usize)
},
EofReader(ref mut body) => {
body.read(buf)
},
EmptyReader(_) => Ok(0)
}
}
}
fn eat<R: Read>(rdr: &mut R, bytes: &[u8]) -> io::Result<()> {
let mut buf = [0];
for &b in bytes.iter() {
match try!(rdr.read(&mut buf)) {
1 if buf[0] == b => (),
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
"Invalid characters found",
None))
}
}
Ok(())
}
fn read_chunk_size<R: Read>(rdr: &mut R) -> io::Result<u64> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
"Invalid chunk size line",
None)),
}
})
);
let mut size = 0u64;
let radix = 16;
let mut in_ext = false;
let mut in_chunk_size = true;
loop {
match byte!(rdr) {
b@b'0'...b'9' if in_chunk_size => {
size *= radix;
size += (b - b'0') as u64;
},
b@b'a'...b'f' if in_chunk_size => {
size *= radix;
size += (b + 10 - b'a') as u64;
},
b@b'A'...b'F' if in_chunk_size => {
size *= radix;
size += (b + 10 - b'A') as u64;
},
CR => {
match byte!(rdr) {
LF => break,
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput,
"Invalid chunk size line",
None))
}
},
b';' if !in_ext => {
in_ext = true;
in_chunk_size = false;
},
b'\t' | b' ' if !in_ext & !in_chunk_size => {},
b'\t' | b' ' if in_chunk_size => in_chunk_size = false,
ext if in_ext => {
todo!("chunk extension byte={}", ext);
},
_ => {
return Err(io::Error::new(io::ErrorKind::InvalidInput,
"Invalid chunk size line",
None))
}
}
}
debug!("chunk size={:?}", size);
Ok(size)
}
pub enum HttpWriter<W: Write> {
ThroughWriter(W),
ChunkedWriter(W),
SizedWriter(W, u64),
EmptyWriter(W),
}
impl<W: Write> HttpWriter<W> {
#[inline]
pub fn into_inner(self) -> W {
match self {
ThroughWriter(w) => w,
ChunkedWriter(w) => w,
SizedWriter(w, _) => w,
EmptyWriter(w) => w,
}
}
#[inline]
pub fn get_ref<'a>(&'a self) -> &'a W {
match *self {
ThroughWriter(ref w) => w,
ChunkedWriter(ref w) => w,
SizedWriter(ref w, _) => w,
EmptyWriter(ref w) => w,
}
}
#[inline]
pub fn get_mut<'a>(&'a mut self) -> &'a mut W {
match *self {
ThroughWriter(ref mut w) => w,
ChunkedWriter(ref mut w) => w,
SizedWriter(ref mut w, _) => w,
EmptyWriter(ref mut w) => w,
}
}
#[inline]
pub fn end(mut self) -> io::Result<W> {
try!(self.write(&[]));
try!(self.flush());
Ok(self.into_inner())
}
}
impl<W: Write> Write for HttpWriter<W> {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match *self {
ThroughWriter(ref mut w) => w.write(msg),
ChunkedWriter(ref mut w) => {
let chunk_size = msg.len();
debug!("chunked write, size = {:?}", chunk_size);
try!(write!(w, "{:X}{}", chunk_size, LINE_ENDING));
try!(w.write_all(msg));
try!(w.write_all(LINE_ENDING.as_bytes()));
Ok(msg.len())
},
SizedWriter(ref mut w, ref mut remaining) => {
let len = msg.len() as u64;
if len > *remaining {
let len = *remaining;
*remaining = 0;
try!(w.write_all(&msg[..len as usize]));
Ok(len as usize)
} else {
*remaining -= len;
try!(w.write_all(msg));
Ok(len as usize)
}
},
EmptyWriter(..) => {
if msg.len() != 0 {
error!("Cannot include a body with this kind of message");
}
Ok(0)
}
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match *self {
ThroughWriter(ref mut w) => w.flush(),
ChunkedWriter(ref mut w) => w.flush(),
SizedWriter(ref mut w, _) => w.flush(),
EmptyWriter(ref mut w) => w.flush(),
}
}
}
pub fn parse_request<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<(Method, RequestUri)>> {
let (inc, len) = {
let slice = try!(buf.fill_buf());
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
let mut req = httparse::Request::new(&mut headers);
match try!(req.parse(slice)) {
httparse::Status::Complete(len) => {
(Incoming {
version: if req.version.unwrap() == 1 { Http11 } else { Http10 },
subject: (
try!(req.method.unwrap().parse()),
try!(req.path.unwrap().parse())
),
headers: try!(Headers::from_raw(req.headers))
}, len)
},
_ => {
return Err(HttpTooLargeError)
}
}
};
buf.consume(len);
Ok(inc)
}
pub fn parse_response<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<RawStatus>> {
let (inc, len) = {
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
let mut res = httparse::Response::new(&mut headers);
match try!(res.parse(try!(buf.fill_buf()))) {
httparse::Status::Complete(len) => {
(Incoming {
version: if res.version.unwrap() == 1 { Http11 } else { Http10 },
subject: RawStatus(
res.code.unwrap(), res.reason.unwrap().to_owned().into_cow()
),
headers: try!(Headers::from_raw(res.headers))
}, len)
},
_ => {
return Err(HttpTooLargeError)
}
}
};
buf.consume(len);
Ok(inc)
}
pub struct Incoming<S> {
pub version: HttpVersion,
pub subject: S,
pub headers: Headers
}
pub const SP: u8 = b' ';
pub const CR: u8 = b'\r';
pub const LF: u8 = b'\n';
pub const STAR: u8 = b'*';
pub const LINE_ENDING: &'static str = "\r\n";
#[derive(PartialEq, Debug)]
pub struct RawStatus(pub u16, pub Cow<'static, str>);
impl Clone for RawStatus {
fn clone(&self) -> RawStatus {
RawStatus(self.0, self.1.clone().into_cow())
}
}
#[cfg(test)]
mod tests {
use std::io::{self, Write};
use super::{read_chunk_size};
#[test]
fn test_write_chunked() {
use std::str::from_utf8;
let mut w = super::HttpWriter::ChunkedWriter(Vec::new());
w.write_all(b"foo bar").unwrap();
w.write_all(b"baz quux herp").unwrap();
let buf = w.end().unwrap();
let s = from_utf8(buf.as_ref()).unwrap();
assert_eq!(s, "7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n");
}
#[test]
fn test_write_sized() {
use std::str::from_utf8;
let mut w = super::HttpWriter::SizedWriter(Vec::new(), 8);
w.write_all(b"foo bar").unwrap();
assert_eq!(w.write(b"baz"), Ok(1));
let buf = w.end().unwrap();
let s = from_utf8(buf.as_ref()).unwrap();
assert_eq!(s, "foo barb");
}
#[test]
fn test_read_chunk_size() {
fn read(s: &str, result: io::Result<u64>) {
assert_eq!(read_chunk_size(&mut s.as_bytes()), result);
}
fn read_err(s: &str) {
assert_eq!(read_chunk_size(&mut s.as_bytes()).unwrap_err().kind(), io::ErrorKind::InvalidInput);
}
read("1\r\n", Ok(1));
read("01\r\n", Ok(1));
read("0\r\n", Ok(0));
read("00\r\n", Ok(0));
read("A\r\n", Ok(10));
read("a\r\n", Ok(10));
read("Ff\r\n", Ok(255));
read("Ff \r\n", Ok(255));
read_err("F\rF");
read_err("F");
read_err("X\r\n");
read_err("1X\r\n");
read_err("-\r\n");
read_err("-1\r\n");
read("1;extension\r\n", Ok(1));
read("a;ext name=value\r\n", Ok(10));
read("1;extension;extension2\r\n", Ok(1));
read("1;;; ;\r\n", Ok(1));
read("2; extension...\r\n", Ok(2));
read("3 ; extension=123\r\n", Ok(3));
read("3 ;\r\n", Ok(3));
read("3 ; \r\n", Ok(3));
read_err("1 invalid extension\r\n");
read_err("1 A\r\n");
read_err("1;no CRLF");
}
use test::Bencher;
#[bench]
fn bench_parse_incoming(b: &mut Bencher) {
use std::io::BufReader;
use mock::MockStream;
use super::parse_request;
b.iter(|| {
let mut raw = MockStream::with_input(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n");
let mut buf = BufReader::new(&mut raw);
parse_request(&mut buf).unwrap();
});
}
}