diff --git a/src/client/response.rs b/src/client/response.rs index f0100290..39196a41 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,15 +1,17 @@ //! Client Responses +use std::num::FromPrimitive; use std::io::{BufferedReader, IoResult}; use header; use header::common::{ContentLength, TransferEncoding}; use header::common::transfer_encoding::Encoding::Chunked; use net::{NetworkStream, HttpStream}; -use http::{read_status_line, HttpReader}; +use http::{read_status_line, HttpReader, RawStatus}; use http::HttpReader::{SizedReader, ChunkedReader, EofReader}; use status; use version; use HttpResult; +use HttpError::HttpStatusError; /// A response for a client request to a remote server. pub struct Response { @@ -19,6 +21,7 @@ pub struct Response { pub headers: header::Headers, /// The HTTP version of this response from the server. pub version: version::HttpVersion, + status_raw: RawStatus, body: HttpReader>>, } @@ -27,10 +30,14 @@ impl Response { /// Creates a new response from a server. pub fn new(stream: Box) -> HttpResult { let mut stream = BufferedReader::new(stream); - let (version, status) = try!(read_status_line(&mut stream)); - let headers = try!(header::Headers::from_raw(&mut stream)); - + let (version, raw_status) = try!(read_status_line(&mut stream)); + let status = match FromPrimitive::from_u16(raw_status.0) { + Some(status) => status, + None => return Err(HttpStatusError) + }; debug!("{} {}", version, status); + + let headers = try!(header::Headers::from_raw(&mut stream)); debug!("{}", headers); let body = if headers.has::() { @@ -64,9 +71,15 @@ impl Response { version: version, headers: headers, body: body, + status_raw: raw_status, }) } + /// Get the raw status code and reason. + pub fn status_raw(&self) -> &RawStatus { + &self.status_raw + } + /// Unwraps the Request to return the NetworkStream underneath. pub fn unwrap(self) -> Box { self.body.unwrap().unwrap() @@ -84,9 +97,11 @@ impl Reader for Response { mod tests { use std::boxed::BoxAny; use std::io::BufferedReader; + use std::str::Slice; use header::Headers; use http::HttpReader::EofReader; + use http::RawStatus; use mock::MockStream; use net::NetworkStream; use status; @@ -101,7 +116,8 @@ mod tests { status: status::StatusCode::Ok, headers: Headers::new(), version: version::HttpVersion::Http11, - body: EofReader(BufferedReader::new(box MockStream::new() as Box)) + body: EofReader(BufferedReader::new(box MockStream::new() as Box)), + status_raw: RawStatus(200, Slice("OK")) }; let b = res.unwrap().downcast::().unwrap(); diff --git a/src/http.rs b/src/http.rs index ac68508a..53f30a86 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,13 +1,14 @@ //! Pieces pertaining to the HTTP message protocol. use std::cmp::min; use std::fmt; -use std::io::{mod, Reader, IoResult}; -use std::str; +use std::io::{mod, Reader, IoResult, BufWriter}; +use std::num::from_u16; +use std::str::{mod, SendStr, Slice, Owned}; use url::Url; use method; -use status; +use status::StatusCode; use uri; use uri::RequestUri::{AbsolutePath, AbsoluteUri, Authority, Star}; use version::HttpVersion; @@ -548,7 +549,11 @@ pub fn read_request_line(stream: &mut R) -> HttpResult { /// `status-line = HTTP-version SP status-code SP reason-phrase CRLF` /// /// However, reason-phrase is absolutely useless, so its tossed. -pub type StatusLine = (HttpVersion, status::StatusCode); +pub type StatusLine = (HttpVersion, RawStatus); + +/// The raw status code and reason-phrase. +#[deriving(PartialEq, Show, Clone)] +pub struct RawStatus(pub u16, pub SendStr); /// Read the StatusLine, such as `HTTP/1.1 200 OK`. /// @@ -573,7 +578,7 @@ pub fn read_status_line(stream: &mut R) -> HttpResult { } /// Read the StatusCode from a stream. -pub fn read_status(stream: &mut R) -> HttpResult { +pub fn read_status(stream: &mut R) -> HttpResult { let code = [ try!(stream.read_byte()), try!(stream.read_byte()), @@ -581,25 +586,56 @@ pub fn read_status(stream: &mut R) -> HttpResult ]; let code = match str::from_utf8(code.as_slice()).and_then(from_str::) { - Some(num) => match FromPrimitive::from_u16(num) { - Some(code) => code, - None => return Err(HttpStatusError) + Some(num) => num, + None => return Err(HttpStatusError) + }; + + match try!(stream.read_byte()) { + b' ' => (), + _ => return Err(HttpStatusError) + } + + let mut buf = [b' ', ..16]; + + { + let mut bufwrt = BufWriter::new(&mut buf); + 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(_) => { + // what sort of reason phrase is this long? + return Err(HttpStatusError); + } + } + } + } + } + + let reason = match str::from_utf8(buf[]) { + Some(s) => s.trim(), + None => return Err(HttpStatusError) + }; + + let reason = match from_u16::(code) { + Some(status) => match status.canonical_reason() { + Some(phrase) => { + if phrase == reason { + Slice(phrase) + } else { + Owned(reason.into_string()) + } + } + _ => Owned(reason.into_string()) }, None => return Err(HttpStatusError) }; - // reason is purely for humans, so just consume it till we get to CRLF - loop { - match try!(stream.read_byte()) { - CR => match try!(stream.read_byte()) { - LF => break, - _ => return Err(HttpStatusError) - }, - _ => () - } - } - - Ok(code) + Ok(RawStatus(code, reason)) } #[inline] @@ -614,18 +650,19 @@ fn expect(r: IoResult, expected: u8) -> HttpResult<()> { #[cfg(test)] mod tests { use std::io::{mod, MemReader, MemWriter}; + use std::str::{Slice, Owned}; use test::Bencher; use uri::RequestUri; use uri::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority}; use method; - use status; 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}; + 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()) @@ -679,11 +716,13 @@ mod tests { #[test] fn test_read_status() { - fn read(s: &str, result: HttpResult) { + fn read(s: &str, result: HttpResult) { assert_eq!(read_status(&mut mem(s)), result); } - read("200 OK\r\n", Ok(status::StatusCode::Ok)); + read("200 OK\r\n", Ok(RawStatus(200, Slice("OK")))); + read("404 Not Found\r\n", Ok(RawStatus(404, Slice("Not Found")))); + read("200 crazy pants\r\n", Ok(RawStatus(200, Owned("crazy pants".to_string())))); } #[test] @@ -725,4 +764,10 @@ mod tests { 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, Slice("Not Found"))))); + } + }