hyper 0.0.15

A modern HTTP library.
//! Pieces pertaining to the HTTP message protocol.
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};

/// Readers to handle different Transfer-Encodings.
///
/// If a message body does not include a Transfer-Encoding, it *should*
/// include a Content-Length header.
pub enum HttpReader<R> {
    /// A Reader used when a Content-Length header is passed with a positive integer.
    SizedReader(R, uint),
    /// A Reader used when Transfer-Encoding is `chunked`.
    ChunkedReader(R, Option<uint>),
    /// A Reader used for responses that don't indicate a length or chunked.
    ///
    /// Note: This should only used for `Response`s. It is illegal for a
    /// `Request` to be made with both `Content-Length` and
    /// `Transfer-Encoding: chunked` missing, as explained from the spec:
    ///
    /// > If a Transfer-Encoding header field is present in a response and
    /// > the chunked transfer coding is not the final encoding, the
    /// > message body length is determined by reading the connection until
    /// > it is closed by the server.  If a Transfer-Encoding header field
    /// > is present in a request and the chunked transfer coding is not
    /// > the final encoding, the message body length cannot be determined
    /// > reliably; the server MUST respond with the 400 (Bad Request)
    /// > status code and then close the connection.
    EofReader(R),
    /// A Reader used for messages that should never have a body.
    ///
    /// See https://tools.ietf.org/html/rfc7230#section-3.3.3
    EmptyReader(R),
}

impl<R: Reader> HttpReader<R> {

    /// Unwraps this HttpReader and returns the underlying Reader.
    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 means we don't know the size of the next chunk
                    None => try!(read_chunk_size(body))
                };
                debug!("Chunked read, remaining={}", rem);

                if rem == 0 {
                    *opt_remaining = Some(0);

                    // chunk of size 0 signals the end of the chunked stream
                    // if the 0 digit was missing from the stream, it would
                    // be an InvalidInput error instead.
                    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(())
}

/// Chunked chunks start with 1*HEXDIGIT, indicating the size of the chunk.
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)
}

/// Writers to handle different Transfer-Encodings.
pub enum HttpWriter<W: Writer> {
    /// A no-op Writer, used initially before Transfer-Encoding is determined.
    ThroughWriter(W),
    /// A Writer for when Transfer-Encoding includes `chunked`.
    ChunkedWriter(W),
    /// A Writer for when Content-Length is set.
    ///
    /// Enforces that the body is not longer than the Content-Length header.
    SizedWriter(W, uint),
    /// A writer that should not write any body.
    EmptyWriter(W),
}

impl<W: Writer> HttpWriter<W> {
    /// Unwraps the HttpWriter and returns the underlying Writer.
    #[inline]
    pub fn unwrap(self) -> W {
        match self {
            ThroughWriter(w) => w,
            ChunkedWriter(w) => w,
            SizedWriter(w, _) => w,
            EmptyWriter(w) => w,
        }
    }

    /// Access the inner Writer.
    #[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,
        }
    }

    /// Access the inner Writer mutably.
    ///
    /// Warning: You should not write to this directly, as you can corrupt
    /// the state.
    #[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,
        }
    }

    /// Ends the HttpWriter, and returns the underlying Writer.
    ///
    /// A final `write()` is called with an empty message, and then flushed.
    /// The ChunkedWriter variant will use this to write the 0-sized last-chunk.
    #[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))); // msg[...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];

/// A `Show`able struct to easily write line endings to a formatter.
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
    }
}

/// Determines if byte is a token char.
///
/// > ```notrust
/// > token          = 1*tchar
/// >
/// > tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/// >                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/// >                / DIGIT / ALPHA
/// >                ; any VCHAR, except delimiters
/// > ```
#[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
    }
}

/// Read token bytes from `stream` into `buf` until a space is encountered.
/// Returns `Ok(true)` if we read until a space,
/// `Ok(false)` if we got to the end of `buf` without encountering a space,
/// otherwise returns any error encountered reading the stream.
///
/// The remaining contents of `buf` are left untouched.
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);
        // Read to end but there's still more
        } else if bufwrt.write_u8(byte).is_err() {
            return Ok(false);
        }
    }

    if bufwrt.tell().unwrap() == 0 {
        return Err(HttpMethodError);
    }

    Ok(true)
}

/// Read a `Method` from a raw stream, such as `GET`.
/// ### Note:
/// Extension methods are only parsed to 16 characters.
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) => {
            // We already checked that the buffer is ASCII
            Ok(method::Method::Extension(unsafe { str::from_utf8_unchecked(ext) }.trim().into_string()))
        },
    }
}

/// Read a `RequestUri` from a raw stream.
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))
    }


}


/// Read the `HttpVersion` from a raw stream, such as `HTTP/1.1`.
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;

/// The raw bytes when parsing a header line.
///
/// A String and Vec<u8>, divided by COLON (`:`). The String is guaranteed
/// to be all `token`s. See `is_token` source for all valid characters.
pub type RawHeaderLine = (String, Vec<u8>);

/// Read a RawHeaderLine from a Reader.
///
/// From [spec](https://tools.ietf.org/html/http#section-3.2):
///
/// > Each header field consists of a case-insensitive field name followed
/// > by a colon (":"), optional leading whitespace, the field value, and
/// > optional trailing whitespace.
/// >
/// > ```notrust
/// > header-field   = field-name ":" OWS field-value OWS
/// >
/// > field-name     = token
/// > field-value    = *( field-content / obs-fold )
/// > field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
/// > field-vchar    = VCHAR / obs-text
/// >
/// > obs-fold       = CRLF 1*( SP / HTAB )
/// >                ; obsolete line folding
/// >                ; see Section 3.2.4
/// > ```
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; //optional whitespace

    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)
    }

}

/// `request-line   = method SP request-target SP HTTP-version CRLF`
pub type RequestLine = (method::Method, uri::RequestUri, HttpVersion);

/// Read the `RequestLine`, such as `GET / HTTP/1.1`.
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))
}

/// `status-line = HTTP-version SP status-code SP reason-phrase CRLF`
///
/// However, reason-phrase is absolutely useless, so its tossed.
pub type StatusLine = (HttpVersion, RawStatus);

/// The raw status code and reason-phrase.
#[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())
    }
}

/// Read the StatusLine, such as `HTTP/1.1 200 OK`.
///
/// > The first line of a response message is the status-line, consisting
/// > of the protocol version, a space (SP), the status code, another
/// > space, a possibly empty textual phrase describing the status code,
/// > and ending with CRLF.
/// >
/// >```notrust
/// > status-line = HTTP-version SP status-code SP reason-phrase CRLF
/// > status-code    = 3DIGIT
/// > reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
/// >```
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))
}

/// Read the StatusCode from a stream.
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)
                                },
                                _ => { /* ignore */ }
                            }
                        }
                        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")))));
    }

}