use std::borrow::Cow::{Borrowed, Owned};
use std::cmp::min;
use std::fmt;
use std::io::{mod, Reader, IoResult, BufWriter};
use std::num::from_u16;
use std::str::{mod, SendStr};
use url::Url;
use url::ParseError as UrlError;
use method;
use status::StatusCode;
use uri;
use uri::RequestUri::{AbsolutePath, AbsoluteUri, Authority, Star};
use version::HttpVersion;
use version::HttpVersion::{Http09, Http10, Http11, Http20};
use HttpError::{HttpHeaderError, HttpIoError, HttpMethodError, HttpStatusError,
HttpUriError, HttpVersionError};
use HttpResult;
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
pub enum HttpReader<R> {
SizedReader(R, uint),
ChunkedReader(R, Option<uint>),
EofReader(R),
EmptyReader(R),
}
impl<R: Reader> HttpReader<R> {
pub fn unwrap(self) -> R {
match self {
SizedReader(r, _) => r,
ChunkedReader(r, _) => r,
EofReader(r) => r,
EmptyReader(r) => r,
}
}
}
impl<R: Reader> Reader for HttpReader<R> {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
match *self {
SizedReader(ref mut body, ref mut remaining) => {
debug!("Sized read, remaining={}", remaining);
if *remaining == 0 {
Err(io::standard_error(io::EndOfFile))
} else {
let num = try!(body.read(buf));
if num > *remaining {
*remaining = 0;
} else {
*remaining -= num;
}
Ok(num)
}
},
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 Err(io::standard_error(io::EndOfFile));
}
let to_read = min(rem, buf.len());
let count = try!(body.read(buf.slice_to_mut(to_read)));
rem -= count;
*opt_remaining = if rem > 0 {
Some(rem)
} else {
try!(eat(body, LINE_ENDING));
None
};
Ok(count)
},
EofReader(ref mut body) => {
body.read(buf)
},
EmptyReader(_) => Err(io::standard_error(io::EndOfFile))
}
}
}
fn eat<R: Reader>(rdr: &mut R, bytes: &[u8]) -> IoResult<()> {
for &b in bytes.iter() {
match try!(rdr.read_byte()) {
byte if byte == b => (),
_ => return Err(io::standard_error(io::InvalidInput))
}
}
Ok(())
}
fn read_chunk_size<R: Reader>(rdr: &mut R) -> IoResult<uint> {
let mut size = 0u;
let radix = 16;
let mut in_ext = false;
loop {
match try!(rdr.read_byte()) {
b@b'0'...b'9' if !in_ext => {
size *= radix;
size += (b - b'0') as uint;
},
b@b'a'...b'f' if !in_ext => {
size *= radix;
size += (b + 10 - b'a') as uint;
},
b@b'A'...b'F' if !in_ext => {
size *= radix;
size += (b + 10 - b'A') as uint;
},
CR => {
match try!(rdr.read_byte()) {
LF => break,
_ => return Err(io::standard_error(io::InvalidInput))
}
},
ext => {
in_ext = true;
todo!("chunk extension byte={}", ext);
}
}
}
debug!("chunk size={}", size);
Ok(size)
}
pub enum HttpWriter<W: Writer> {
ThroughWriter(W),
ChunkedWriter(W),
SizedWriter(W, uint),
EmptyWriter(W),
}
impl<W: Writer> HttpWriter<W> {
#[inline]
pub fn unwrap(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) -> IoResult<W> {
try!(self.write(&[]));
try!(self.flush());
Ok(self.unwrap())
}
}
impl<W: Writer> Writer for HttpWriter<W> {
#[inline]
fn write(&mut self, msg: &[u8]) -> IoResult<()> {
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, CR as char, LF as char));
try!(w.write(msg));
w.write(LINE_ENDING)
},
SizedWriter(ref mut w, ref mut remaining) => {
let len = msg.len();
if len > *remaining {
let len = *remaining;
*remaining = 0;
try!(w.write(msg.slice_to(len))); Err(io::standard_error(io::ShortWrite(len)))
} else {
*remaining -= len;
w.write(msg)
}
},
EmptyWriter(..) => {
let bytes = msg.len();
if bytes == 0 {
Ok(())
} else {
Err(io::IoError {
kind: io::ShortWrite(bytes),
desc: "EmptyWriter cannot write any bytes",
detail: Some("Cannot include a body with this kind of message".into_string())
})
}
}
}
}
#[inline]
fn flush(&mut self) -> IoResult<()> {
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 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 [u8] = &[CR, LF];
pub struct LineEnding;
impl Copy for LineEnding {}
impl fmt::Show for LineEnding {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write(LINE_ENDING)
}
}
impl AsSlice<u8> for LineEnding {
fn as_slice(&self) -> &[u8] {
LINE_ENDING
}
}
#[inline]
pub fn is_token(b: u8) -> bool {
match b {
b'a'...b'z' |
b'A'...b'Z' |
b'0'...b'9' |
b'!' |
b'#' |
b'$' |
b'%' |
b'&' |
b'\''|
b'*' |
b'+' |
b'-' |
b'.' |
b'^' |
b'_' |
b'`' |
b'|' |
b'~' => true,
_ => false
}
}
fn read_token_until_space<R: Reader>(stream: &mut R, buf: &mut [u8]) -> HttpResult<bool> {
use std::io::BufWriter;
let mut bufwrt = BufWriter::new(buf);
loop {
let byte = try!(stream.read_byte());
if byte == SP {
break;
} else if !is_token(byte) {
return Err(HttpMethodError);
} else if bufwrt.write_u8(byte).is_err() {
return Ok(false);
}
}
if bufwrt.tell().unwrap() == 0 {
return Err(HttpMethodError);
}
Ok(true)
}
pub fn read_method<R: Reader>(stream: &mut R) -> HttpResult<method::Method> {
let mut buf = [SP, ..16];
if !try!(read_token_until_space(stream, &mut buf)) {
return Err(HttpMethodError);
}
debug!("method buf = {}", buf[].to_ascii());
let maybe_method = match buf[0..7] {
b"GET " => Some(method::Method::Get),
b"PUT " => Some(method::Method::Put),
b"POST " => Some(method::Method::Post),
b"HEAD " => Some(method::Method::Head),
b"PATCH " => Some(method::Method::Patch),
b"TRACE " => Some(method::Method::Trace),
b"DELETE " => Some(method::Method::Delete),
b"CONNECT" => Some(method::Method::Connect),
b"OPTIONS" => Some(method::Method::Options),
_ => None,
};
debug!("maybe_method = {}", maybe_method);
match (maybe_method, buf[]) {
(Some(method), _) => Ok(method),
(None, ext) => {
Ok(method::Method::Extension(unsafe { str::from_utf8_unchecked(ext) }.trim().into_string()))
},
}
}
pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> {
let mut b = try!(stream.read_byte());
while b == SP {
b = try!(stream.read_byte());
}
let mut s = String::new();
if b == STAR {
try!(expect(stream.read_byte(), SP));
return Ok(Star)
} else {
s.push(b as char);
loop {
match try!(stream.read_byte()) {
SP => {
break;
},
CR | LF => {
return Err(HttpUriError(UrlError::InvalidCharacter))
},
b => s.push(b as char)
}
}
}
debug!("uri buf = {}", s);
if s.as_slice().starts_with("/") {
Ok(AbsolutePath(s))
} else if s.as_slice().contains("/") {
Ok(AbsoluteUri(try!(Url::parse(s.as_slice()))))
} else {
let mut temp = "http://".to_string();
temp.push_str(s.as_slice());
try!(Url::parse(temp.as_slice()));
todo!("compare vs u.authority()");
Ok(Authority(s))
}
}
pub fn read_http_version<R: Reader>(stream: &mut R) -> HttpResult<HttpVersion> {
try!(expect(stream.read_byte(), b'H'));
try!(expect(stream.read_byte(), b'T'));
try!(expect(stream.read_byte(), b'T'));
try!(expect(stream.read_byte(), b'P'));
try!(expect(stream.read_byte(), b'/'));
match try!(stream.read_byte()) {
b'0' => {
try!(expect(stream.read_byte(), b'.'));
try!(expect(stream.read_byte(), b'9'));
Ok(Http09)
},
b'1' => {
try!(expect(stream.read_byte(), b'.'));
match try!(stream.read_byte()) {
b'0' => Ok(Http10),
b'1' => Ok(Http11),
_ => Err(HttpVersionError)
}
},
b'2' => {
try!(expect(stream.read_byte(), b'.'));
try!(expect(stream.read_byte(), b'0'));
Ok(Http20)
},
_ => Err(HttpVersionError)
}
}
const MAX_HEADER_NAME_LENGTH: uint = 100;
const MAX_HEADER_FIELD_LENGTH: uint = 1000;
pub type RawHeaderLine = (String, Vec<u8>);
pub fn read_header<R: Reader>(stream: &mut R) -> HttpResult<Option<RawHeaderLine>> {
let mut name = String::new();
let mut value = vec![];
loop {
match try!(stream.read_byte()) {
CR if name.len() == 0 => {
match try!(stream.read_byte()) {
LF => return Ok(None),
_ => return Err(HttpHeaderError)
}
},
b':' => break,
b if is_token(b) => {
if name.len() > MAX_HEADER_NAME_LENGTH { return Err(HttpHeaderError); }
name.push(b as char)
},
_nontoken => return Err(HttpHeaderError)
};
}
debug!("header name = {}", name);
let mut ows = true;
todo!("handle obs-folding (gross!)");
loop {
match try!(stream.read_byte()) {
CR => break,
LF => return Err(HttpHeaderError),
b' ' if ows => {},
b => {
ows = false;
if value.len() > MAX_HEADER_FIELD_LENGTH { return Err(HttpHeaderError); }
value.push(b)
}
};
}
debug!("header value = {}", value[].to_ascii());
match try!(stream.read_byte()) {
LF => Ok(Some((name, value))),
_ => Err(HttpHeaderError)
}
}
pub type RequestLine = (method::Method, uri::RequestUri, HttpVersion);
pub fn read_request_line<R: Reader>(stream: &mut R) -> HttpResult<RequestLine> {
debug!("read request line");
let method = try!(read_method(stream));
debug!("method = {}", method);
let uri = try!(read_uri(stream));
debug!("uri = {}", uri);
let version = try!(read_http_version(stream));
debug!("version = {}", version);
if try!(stream.read_byte()) != CR {
return Err(HttpVersionError);
}
if try!(stream.read_byte()) != LF {
return Err(HttpVersionError);
}
Ok((method, uri, version))
}
pub type StatusLine = (HttpVersion, RawStatus);
#[deriving(PartialEq, Show)]
pub struct RawStatus(pub u16, pub SendStr);
impl Clone for RawStatus {
fn clone(&self) -> RawStatus {
RawStatus(self.0, self.1.clone().into_cow())
}
}
pub fn read_status_line<R: Reader>(stream: &mut R) -> HttpResult<StatusLine> {
let version = try!(read_http_version(stream));
if try!(stream.read_byte()) != SP {
return Err(HttpVersionError);
}
let code = try!(read_status(stream));
Ok((version, code))
}
pub fn read_status<R: Reader>(stream: &mut R) -> HttpResult<RawStatus> {
let code = [
try!(stream.read_byte()),
try!(stream.read_byte()),
try!(stream.read_byte()),
];
let code = match str::from_utf8(code.as_slice()).and_then(from_str::<u16>) {
Some(num) => num,
None => return Err(HttpStatusError)
};
match try!(stream.read_byte()) {
b' ' => (),
_ => return Err(HttpStatusError)
}
let mut buf = [b' ', ..32];
{
let mut bufwrt = BufWriter::new(&mut buf);
'read: loop {
match try!(stream.read_byte()) {
CR => match try!(stream.read_byte()) {
LF => break,
_ => return Err(HttpStatusError)
},
b => match bufwrt.write_u8(b) {
Ok(_) => (),
Err(_) => {
for _ in range(0u, 128) {
match try!(stream.read_byte()) {
CR => match try!(stream.read_byte()) {
LF => break 'read,
_ => return Err(HttpStatusError)
},
_ => { }
}
}
return Err(HttpStatusError)
}
}
}
}
}
let reason = match str::from_utf8(buf[]) {
Some(s) => s.trim(),
None => return Err(HttpStatusError)
};
let reason = match from_u16::<StatusCode>(code) {
Some(status) => match status.canonical_reason() {
Some(phrase) => {
if phrase == reason {
Borrowed(phrase)
} else {
Owned(reason.into_string())
}
}
_ => Owned(reason.into_string())
},
None => return Err(HttpStatusError)
};
Ok(RawStatus(code, reason))
}
#[inline]
fn expect(r: IoResult<u8>, expected: u8) -> HttpResult<()> {
match r {
Ok(b) if b == expected => Ok(()),
Ok(_) => Err(HttpVersionError),
Err(e) => Err(HttpIoError(e))
}
}
#[cfg(test)]
mod tests {
use std::io::{mod, MemReader, MemWriter};
use std::borrow::Cow::{Borrowed, Owned};
use test::Bencher;
use uri::RequestUri;
use uri::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority};
use method;
use version::HttpVersion;
use version::HttpVersion::{Http10, Http11, Http20};
use HttpError::{HttpVersionError, HttpMethodError};
use HttpResult;
use url::Url;
use super::{read_method, read_uri, read_http_version, read_header,
RawHeaderLine, read_status, RawStatus};
fn mem(s: &str) -> MemReader {
MemReader::new(s.as_bytes().to_vec())
}
#[test]
fn test_read_method() {
fn read(s: &str, result: HttpResult<method::Method>) {
assert_eq!(read_method(&mut mem(s)), result);
}
read("GET /", Ok(method::Method::Get));
read("POST /", Ok(method::Method::Post));
read("PUT /", Ok(method::Method::Put));
read("HEAD /", Ok(method::Method::Head));
read("OPTIONS /", Ok(method::Method::Options));
read("CONNECT /", Ok(method::Method::Connect));
read("TRACE /", Ok(method::Method::Trace));
read("PATCH /", Ok(method::Method::Patch));
read("FOO /", Ok(method::Method::Extension("FOO".to_string())));
read("akemi!~#HOMURA /", Ok(method::Method::Extension("akemi!~#HOMURA".to_string())));
read(" ", Err(HttpMethodError));
}
#[test]
fn test_read_uri() {
fn read(s: &str, result: HttpResult<RequestUri>) {
assert_eq!(read_uri(&mut mem(s)), result);
}
read("* ", Ok(Star));
read("http://hyper.rs/ ", Ok(AbsoluteUri(Url::parse("http://hyper.rs/").unwrap())));
read("hyper.rs ", Ok(Authority("hyper.rs".to_string())));
read("/ ", Ok(AbsolutePath("/".to_string())));
}
#[test]
fn test_read_http_version() {
fn read(s: &str, result: HttpResult<HttpVersion>) {
assert_eq!(read_http_version(&mut mem(s)), result);
}
read("HTTP/1.0", Ok(Http10));
read("HTTP/1.1", Ok(Http11));
read("HTTP/2.0", Ok(Http20));
read("HTP/2.0", Err(HttpVersionError));
read("HTTP.2.0", Err(HttpVersionError));
read("HTTP 2.0", Err(HttpVersionError));
read("TTP 2.0", Err(HttpVersionError));
}
#[test]
fn test_read_status() {
fn read(s: &str, result: HttpResult<RawStatus>) {
assert_eq!(read_status(&mut mem(s)), result);
}
fn read_ignore_string(s: &str, result: HttpResult<RawStatus>) {
match (read_status(&mut mem(s)), result) {
(Ok(RawStatus(ref c1, _)), Ok(RawStatus(ref c2, _))) => {
assert_eq!(c1, c2);
},
(r1, r2) => assert_eq!(r1, r2)
}
}
read("200 OK\r\n", Ok(RawStatus(200, Borrowed("OK"))));
read("404 Not Found\r\n", Ok(RawStatus(404, Borrowed("Not Found"))));
read("200 crazy pants\r\n", Ok(RawStatus(200, Owned("crazy pants".to_string()))));
read("301 Moved Permanently\r\n", Ok(RawStatus(301, Owned("Moved Permanently".to_string()))));
read_ignore_string("301 Unreasonably long header that should not happen, \
but some men just want to watch the world burn\r\n",
Ok(RawStatus(301, Owned("Ignored".to_string()))));
}
#[test]
fn test_read_header() {
fn read(s: &str, result: HttpResult<Option<RawHeaderLine>>) {
assert_eq!(read_header(&mut mem(s)), result);
}
read("Host: rust-lang.org\r\n", Ok(Some(("Host".to_string(),
"rust-lang.org".as_bytes().to_vec()))));
}
#[test]
fn test_write_chunked() {
use std::str::from_utf8;
let mut w = super::HttpWriter::ChunkedWriter(MemWriter::new());
w.write(b"foo bar").unwrap();
w.write(b"baz quux herp").unwrap();
let buf = w.end().unwrap().into_inner();
let s = from_utf8(buf.as_slice()).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(MemWriter::new(), 8);
w.write(b"foo bar").unwrap();
assert_eq!(w.write(b"baz"), Err(io::standard_error(io::ShortWrite(1))));
let buf = w.end().unwrap().into_inner();
let s = from_utf8(buf.as_slice()).unwrap();
assert_eq!(s, "foo barb");
}
#[bench]
fn bench_read_method(b: &mut Bencher) {
b.bytes = b"CONNECT ".len() as u64;
b.iter(|| assert_eq!(read_method(&mut mem("CONNECT ")), Ok(method::Method::Connect)));
}
#[bench]
fn bench_read_status(b: &mut Bencher) {
b.bytes = b"404 Not Found\r\n".len() as u64;
b.iter(|| assert_eq!(read_status(&mut mem("404 Not Found\r\n")), Ok(RawStatus(404, Borrowed("Not Found")))));
}
}