use ascii::AsciiString; use chunked_transfer; use encoding::label::encoding_from_whatwg_label; use encoding::DecoderTrap; use std::io::Error as IoError; use std::io::ErrorKind; use std::io::Read; use std::io::Result as IoResult; use error::Error; const DEFAULT_CONTENT_TYPE: &'static str = "text/plain"; const DEFAULT_CHARACTER_SET: &'static str = "utf-8"; pub struct Response { status_line: AsciiString, index: (usize, usize), // index into status_line where we split: HTTP/1.1 200 OK status: u16, headers: Vec
, reader: Option>, } impl ::std::fmt::Debug for Response { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { write!( f, "Response[status: {}, status_text: {}]", self.status(), self.status_text() ) } } impl Response { /// The entire status line like: HTTP/1.1 200 OK pub fn status_line(&self) -> &str { self.status_line.as_str() } /// The http version: HTTP/1.1 pub fn http_version(&self) -> &str { &self.status_line.as_str()[0..self.index.0] } /// The status as a u16: 200 pub fn status(&self) -> &u16 { &self.status } /// The status text: OK pub fn status_text(&self) -> &str { &self.status_line.as_str()[self.index.1 + 1..].trim() } /// The header corresponding header value for the give name, if any. pub fn header<'a>(&self, name: &'a str) -> Option<&str> { self.headers .iter() .find(|h| h.is_name(name)) .map(|h| h.value()) } /// Tells if the response has the named header. pub fn has<'a>(&self, name: &'a str) -> bool { self.header(name).is_some() } /// All headers corresponding values for the give name, or empty vector. pub fn all<'a>(&self, name: &'a str) -> Vec<&str> { self.headers .iter() .filter(|h| h.is_name(name)) .map(|h| h.value()) .collect() } /// Whether the response status is: 200 <= status <= 299 pub fn ok(&self) -> bool { self.status >= 200 && self.status <= 299 } pub fn redirect(&self) -> bool { self.status >= 300 && self.status <= 399 } /// Whether the response status is: 400 <= status <= 499 pub fn client_error(&self) -> bool { self.status >= 400 && self.status <= 499 } /// Whether the response status is: 500 <= status <= 599 pub fn server_error(&self) -> bool { self.status >= 500 && self.status <= 599 } /// Whether the response status is: 400 <= status <= 599 pub fn error(&self) -> bool { self.client_error() || self.server_error() } /// The content type part of the "Content-Type" header without /// the charset. /// /// Example: /// /// ``` /// let resp = ureq::get("https://www.google.com/").call(); /// assert_eq!("text/html; charset=ISO-8859-1", resp.header("content-type").unwrap()); /// assert_eq!("text/html", resp.content_type()); /// ``` pub fn content_type(&self) -> &str { self.header("content-type") .map(|header| { header .find(";") .map(|index| &header[0..index]) .unwrap_or(header) }) .unwrap_or(DEFAULT_CONTENT_TYPE) } pub fn charset(&self) -> &str { self.header("content-type") .and_then(|header| { header.find(";").and_then(|semi| { (&header[semi + 1..]) .find("=") .map(|equal| (&header[semi + equal + 2..]).trim()) }) }) .unwrap_or(DEFAULT_CHARACTER_SET) } pub fn into_reader(self) -> impl Read { let is_chunked = self.header("transfer-encoding") .map(|enc| enc.len() > 0) // whatever it says, do chunked .unwrap_or(false); let len = self.header("content-length").and_then(|l| l.parse::().ok()); let reader = self.reader.expect("No reader in response?!"); match is_chunked { true => Box::new(chunked_transfer::Decoder::new(reader)), false => { match len { Some(len) => Box::new(LimitedRead::new(reader, len)), None => reader, } }, } } pub fn into_string(self) -> IoResult { let encoding = encoding_from_whatwg_label(self.charset()) .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) .unwrap(); let mut buf: Vec = vec![]; self.into_reader().read_to_end(&mut buf)?; Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap()) } pub fn into_json(self) -> IoResult { let reader = self.into_reader(); serde_json::from_reader(reader).map_err(|e| { IoError::new( ErrorKind::InvalidData, format!("Failed to read JSON: {}", e), ) }) } pub fn new(status: u16, status_text: &str, body: &str) -> Self { let r = format!("HTTP/1.1 {} {}\r\n\r\n{}\n", status, status_text, body); (r.as_ref() as &str) .parse::() .unwrap_or_else(|e| e.into()) } pub fn from_read(reader: impl Read) -> Self { Self::do_from_read(reader).unwrap_or_else(|e| e.into()) } fn do_from_read(mut reader: impl Read) -> Result { // // HTTP/1.1 200 OK\r\n let status_line = read_next_line(&mut reader).map_err(|_| Error::BadStatus)?; let (index, status) = parse_status_line(status_line.as_str())?; let mut headers: Vec
= Vec::new(); loop { let line = read_next_line(&mut reader).map_err(|_| Error::BadHeader)?; if line.len() == 0 { break; } if let Ok(header) = line.as_str().parse::
() { headers.push(header); } } Ok(Response { status_line, index, status, headers, reader: None, }) } fn set_reader(&mut self, reader: R) where R: Read + Send + 'static { self.reader = Some(Box::new(reader)); } } fn parse_status_line(line: &str) -> Result<((usize, usize), u16), Error> { // HTTP/1.1 200 OK\r\n let mut split = line.splitn(3, ' '); let http_version = split.next().ok_or_else(|| Error::BadStatus)?; if http_version.len() < 5 { return Err(Error::BadStatus); } let index1 = http_version.len(); let status = split.next().ok_or_else(|| Error::BadStatus)?; if status.len() < 3 { return Err(Error::BadStatus); } let index2 = index1 + status.len(); let status = status.parse::().map_err(|_| Error::BadStatus)?; let status_text = split.next().ok_or_else(|| Error::BadStatus)?; if status_text.len() == 0 { return Err(Error::BadStatus); } Ok(((index1, index2), status)) } impl FromStr for Response { type Err = Error; fn from_str(s: &str) -> Result { let mut read = VecRead::from_str(s); let mut resp = Self::do_from_read(&mut read)?; resp.set_reader(read); Ok(resp) } } impl Into for Error { fn into(self) -> Response { Response::new(self.status(), self.status_text(), &self.body_text()) } } // application/x-www-form-urlencoded, application/json, and multipart/form-data fn read_next_line(reader: &mut R) -> IoResult { let mut buf = Vec::new(); let mut prev_byte_was_cr = false; loop { let byte = reader.bytes().next(); let byte = match byte { Some(b) => try!(b), None => return Err(IoError::new(ErrorKind::ConnectionAborted, "Unexpected EOF")), }; if byte == b'\n' && prev_byte_was_cr { buf.pop(); // removing the '\r' return AsciiString::from_ascii(buf) .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII")); } prev_byte_was_cr = byte == b'\r'; buf.push(byte); } } struct LimitedRead { reader: Box, limit: usize, position: usize, } impl LimitedRead { fn new(reader: Box, limit: usize) -> Self { LimitedRead { reader, limit, position: 0, } } } impl Read for LimitedRead { fn read(&mut self, buf: &mut [u8]) -> IoResult { let left = self.limit - self.position; let from = if left < buf.len() { &mut buf[0..left] } else { buf }; match self.reader.read(from) { Ok(amount) => { self.position += amount; Ok(amount) }, Err(e) => Err(e) } } }