use crate::{connection::HttpStream, Error};
use bytes::Bytes;
use futures_core::Stream;
use pin_project::pin_project;
use std::collections::HashMap;
use std::pin::Pin;
use std::str;
use std::task::{Context, Poll};
use tokio::io::{self, AsyncRead, BufReader};
use tokio_stream::StreamExt;
use tokio_util::io::ReaderStream;
const BACKING_READ_BUFFER_LENGTH: usize = 16 * 1024;
const MAX_CONTENT_LENGTH: usize = 16 * 1024;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Response {
pub status_code: i32,
pub reason_phrase: String,
pub headers: HashMap<String, String>,
pub url: String,
body: Vec<u8>,
}
impl Response {
pub(crate) async fn create(mut parent: ResponseLazy, is_head: bool) -> Result<Response, Error> {
let mut body = Vec::new();
if !is_head && parent.status_code != 204 && parent.status_code != 304 {
while let Some(chunk) = parent.next().await {
let (bytes, length) = chunk?;
body.reserve(length);
body.extend_from_slice(&bytes);
}
}
let ResponseLazy {
status_code,
reason_phrase,
headers,
url,
..
} = parent;
Ok(Response {
status_code,
reason_phrase,
headers,
url,
body,
})
}
pub fn as_str(&self) -> Result<&str, Error> {
match str::from_utf8(&self.body) {
Ok(s) => Ok(s),
Err(err) => Err(Error::InvalidUtf8InBody(err)),
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.body
}
pub fn into_bytes(self) -> Vec<u8> {
self.body
}
#[cfg(feature = "json-using-serde")]
pub fn json<'a, T>(&'a self) -> Result<T, Error>
where
T: serde::de::Deserialize<'a>,
{
let str = match self.as_str() {
Ok(str) => str,
Err(_) => return Err(Error::InvalidUtf8InResponse),
};
match serde_json::from_str(str) {
Ok(json) => Ok(json),
Err(err) => Err(Error::SerdeJsonError(err)),
}
}
}
#[pin_project]
pub struct ResponseLazy {
pub status_code: i32,
pub reason_phrase: String,
pub headers: HashMap<String, String>,
pub url: String,
#[pin]
stream: HttpStreamBytes,
state: HttpStreamState,
max_trailing_headers_size: Option<usize>,
trailing_content: Option<Trailing>,
}
type HttpStreamBytes = ReaderStream<BufReader<HttpStream>>;
impl ResponseLazy {
pub(crate) async fn from_stream(
stream: HttpStream,
max_headers_size: Option<usize>,
max_status_line_len: Option<usize>,
) -> Result<ResponseLazy, Error> {
let mut stream =
ReaderStream::new(BufReader::with_capacity(BACKING_READ_BUFFER_LENGTH, stream));
let ResponseMetadata {
status_code,
reason_phrase,
headers,
state,
max_trailing_headers_size,
trailing_content,
} = read_metadata(&mut stream, max_headers_size, max_status_line_len).await?;
let trailing_content = if trailing_content.is_empty() {
None
} else {
Some(trailing_content)
};
Ok(ResponseLazy {
status_code,
reason_phrase,
headers,
url: String::new(),
stream,
state,
max_trailing_headers_size,
trailing_content,
})
}
}
impl Stream for ResponseLazy {
type Item = Result<(Bytes, usize), Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
use HttpStreamState::*;
let this = self.project();
let stream: Pin<&mut HttpStreamBytes> = this.stream;
let mut prefix = Trailing::new();
if let Some(trailing) = this.trailing_content {
prefix = trailing.clone();
*this.trailing_content = None;
}
match this.state {
EndOnClose => read_until_closed(prefix, stream, cx),
ContentLength(ref mut length) => read_with_content_length(prefix, stream, length, cx),
Chunked(ref mut expecting_chunks, ref mut length, ref mut content_length) => {
read_chunked(
prefix,
stream,
this.headers,
expecting_chunks,
length,
content_length,
*this.max_trailing_headers_size,
cx,
)
}
}
}
}
impl AsyncRead for ResponseLazy {
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.project();
let stream: Pin<&mut HttpStreamBytes> = this.stream;
if let Some(prefix) = this.trailing_content {
buf.put_slice(prefix);
*this.trailing_content = None;
}
match stream.poll_next(cx) {
Poll::Ready(Some(Ok(bytes))) => {
let len = std::cmp::min(buf.remaining(), bytes.len());
buf.put_slice(&bytes[..len]); Poll::Ready(Ok(()))
}
Poll::Ready(Some(Err(e))) => Poll::Ready(Err(e)), Poll::Ready(None) => Poll::Ready(Ok(())), Poll::Pending => Poll::Pending, }
}
}
fn process_prefix(prefix: &[u8]) -> Poll<Option<<ResponseLazy as Stream>::Item>> {
Poll::Ready(Some(Ok((Bytes::copy_from_slice(prefix), prefix.len()))))
}
fn read_until_closed(
prefix: Trailing,
bytes: Pin<&mut HttpStreamBytes>,
cx: &mut Context<'_>,
) -> Poll<Option<<ResponseLazy as Stream>::Item>> {
if !prefix.is_empty() {
return process_prefix(&prefix);
}
match bytes.poll_next(cx) {
Poll::Ready(some_chunk) => match some_chunk {
Some(Ok(chunk)) => {
let len = chunk.len();
Poll::Ready(Some(Ok((chunk, len))))
}
Some(Err(err)) => Poll::Ready(Some(Err(Error::IoError(err)))),
None => Poll::Ready(None),
},
Poll::Pending => Poll::Pending,
}
}
fn read_with_content_length(
prefix: Trailing,
bytes: Pin<&mut HttpStreamBytes>,
content_length: &mut usize,
cx: &mut Context<'_>,
) -> Poll<Option<<ResponseLazy as Stream>::Item>> {
let prefix_len = prefix.len();
if prefix_len > 0 {
*content_length -= prefix_len;
return process_prefix(&prefix);
}
if *content_length > 0 {
match bytes.poll_next(cx) {
Poll::Ready(some_chunk) => match some_chunk {
Some(Ok(chunk)) => {
let len = chunk.len();
*content_length -= len;
Poll::Ready(Some(Ok((
chunk,
(*content_length).min(MAX_CONTENT_LENGTH) + len,
))))
}
Some(Err(err)) => Poll::Ready(Some(Err(Error::IoError(err)))),
None => Poll::Ready(None),
},
Poll::Pending => Poll::Pending,
}
} else {
Poll::Ready(None)
}
}
fn read_trailers(
mut bytes: Pin<&mut HttpStreamBytes>,
headers: &mut HashMap<String, String>,
mut max_headers_size: Option<usize>,
cx: &mut Context<'_>,
prefix: Trailing,
) -> Poll<Result<(), Error>> {
let mut trailing = prefix;
loop {
let trailer_line = read_line(
bytes.as_mut(),
max_headers_size,
Error::HeadersOverflow,
cx,
trailing,
)?;
match trailer_line {
Poll::Ready((trailer_line, trailing_)) => {
trailing = trailing_;
if let Some(ref mut max_headers_size) = max_headers_size {
*max_headers_size -= trailer_line.len() + 2;
}
if let Some((header, value)) = parse_header(trailer_line) {
headers.insert(header, value);
} else {
break;
}
}
Poll::Pending => return Poll::Pending,
}
}
Poll::Ready(Ok(()))
}
#[allow(clippy::too_many_arguments)]
fn read_chunked(
prefix: Trailing,
mut bytes: Pin<&mut HttpStreamBytes>,
headers: &mut HashMap<String, String>,
expecting_more_chunks: &mut bool,
chunk_length: &mut usize,
content_length: &mut usize,
max_trailing_headers_size: Option<usize>,
cx: &mut Context<'_>,
) -> Poll<Option<<ResponseLazy as Stream>::Item>> {
if !*expecting_more_chunks && *chunk_length == 0 {
return Poll::Ready(None);
}
if *chunk_length == 0 {
let (length_line, trailing) = match read_line(
bytes.as_mut(),
Some(1024),
Error::MalformedChunkLength,
cx,
prefix,
) {
Poll::Ready(Ok(line)) => line,
Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))),
Poll::Pending => return Poll::Pending,
};
let incoming_length = if length_line.is_empty() {
0
} else {
let length = if let Some(i) = length_line.find(';') {
length_line[..i].trim()
} else {
length_line.trim()
};
match usize::from_str_radix(length, 16) {
Ok(length) => length,
Err(_) => return Poll::Ready(Some(Err(Error::MalformedChunkLength))),
}
};
if incoming_length == 0 {
match read_trailers(
bytes.as_mut(),
headers,
max_trailing_headers_size,
cx,
trailing,
) {
Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))),
Poll::Pending => return Poll::Pending,
_ => {}
}
*expecting_more_chunks = false;
headers.insert("content-length".to_string(), (*content_length).to_string());
headers.remove("transfer-encoding");
return Poll::Ready(None);
}
*chunk_length = incoming_length;
*content_length += incoming_length;
}
if *chunk_length > 0 {
*chunk_length -= 1;
match bytes.poll_next(cx) {
Poll::Ready(some_chunk) => match some_chunk {
Some(Ok(chunk)) => {
let len = chunk.len();
Poll::Ready(Some(Ok((chunk, len))))
}
Some(Err(err)) => Poll::Ready(Some(Err(Error::IoError(err)))),
None => Poll::Ready(None),
},
Poll::Pending => Poll::Pending,
}
} else {
Poll::Ready(None)
}
}
enum HttpStreamState {
EndOnClose,
ContentLength(usize),
Chunked(bool, usize, usize),
}
struct ResponseMetadata {
status_code: i32,
reason_phrase: String,
headers: HashMap<String, String>,
state: HttpStreamState,
max_trailing_headers_size: Option<usize>,
trailing_content: Trailing,
}
type Trailing = Vec<u8>;
async fn read_line_async(
stream: &mut HttpStreamBytes,
max_len: Option<usize>,
overflow_error: Error,
prefix: Trailing,
) -> Result<(String, Trailing), Error> {
let mut bytes = Vec::with_capacity(32);
let mut new_line_flag = true;
let mut trailing = Trailing::new();
let max_len = max_len.unwrap_or(usize::MAX);
for byte in prefix {
if new_line_flag {
if bytes.len() >= max_len {
return Err(overflow_error);
}
if byte == b'\n' {
if let Some(b'\r') = bytes.last() {
bytes.pop();
}
new_line_flag = false;
} else {
bytes.push(byte);
}
} else {
trailing.push(byte);
}
}
if new_line_flag {
while let Some(some_chunk) = stream.next().await {
match some_chunk {
Ok(chunk) => {
for byte in chunk {
if new_line_flag {
if bytes.len() >= max_len {
return Err(overflow_error);
}
if byte == b'\n' {
if let Some(b'\r') = bytes.last() {
bytes.pop();
}
new_line_flag = false;
} else {
bytes.push(byte);
}
} else {
trailing.push(byte);
}
}
if !new_line_flag {
break;
}
}
Err(err) => return Err(Error::IoError(err)),
}
}
}
match String::from_utf8(bytes) {
Ok(line) => Ok((line, trailing)),
Err(_) => Err(Error::InvalidUtf8InResponse),
}
}
async fn read_metadata(
stream: &mut HttpStreamBytes,
mut max_headers_size: Option<usize>,
max_status_line_len: Option<usize>,
) -> Result<ResponseMetadata, Error> {
let mut trailing = Trailing::new();
let (line, trailing_) = read_line_async(
stream,
max_status_line_len,
Error::StatusLineOverflow,
trailing,
)
.await?;
trailing = trailing_;
let (status_code, reason_phrase) = parse_status_line(&line);
let mut headers = HashMap::new();
loop {
let (line, trailing_) =
read_line_async(stream, max_headers_size, Error::HeadersOverflow, trailing).await?;
trailing = trailing_;
if line.is_empty() {
break;
}
if let Some(ref mut max_headers_size) = max_headers_size {
*max_headers_size -= line.len() + 2;
}
if let Some(header) = parse_header(line) {
headers.insert(header.0, header.1);
}
}
let mut chunked = false;
let mut content_length = None;
for (header, value) in &headers {
if header.to_lowercase().trim() == "transfer-encoding"
&& value.to_lowercase().trim() == "chunked"
{
chunked = true;
}
if header.to_lowercase().trim() == "content-length" {
match str::parse::<usize>(value.trim()) {
Ok(length) => content_length = Some(length),
Err(_) => return Err(Error::MalformedContentLength),
}
}
}
let state = if chunked {
HttpStreamState::Chunked(true, 0, 0)
} else if let Some(length) = content_length {
HttpStreamState::ContentLength(length)
} else {
HttpStreamState::EndOnClose
};
Ok(ResponseMetadata {
status_code,
reason_phrase,
headers,
state,
max_trailing_headers_size: max_headers_size,
trailing_content: trailing,
})
}
fn read_line(
mut stream: Pin<&mut HttpStreamBytes>,
max_len: Option<usize>,
overflow_error: Error,
cx: &mut Context<'_>,
prefix: Trailing,
) -> Poll<Result<(String, Trailing), Error>> {
let mut bytes = Vec::with_capacity(32);
let mut new_line_flag = true;
let mut trailing = Trailing::new();
for byte in prefix {
if new_line_flag {
if byte == b'\n' {
if let Some(b'\r') = bytes.last() {
bytes.pop();
}
new_line_flag = false;
} else {
bytes.push(byte);
}
} else {
trailing.push(byte);
}
}
if new_line_flag {
loop {
match stream.as_mut().poll_next(cx) {
Poll::Ready(some_chunk) => match some_chunk {
Some(Ok(chunk)) => {
if let Some(max_len) = max_len {
if bytes.len() >= max_len {
return Poll::Ready(Err(overflow_error));
}
}
for byte in chunk {
if new_line_flag {
if byte == b'\n' {
if let Some(b'\r') = bytes.last() {
bytes.pop();
}
new_line_flag = false;
} else {
bytes.push(byte);
}
} else {
trailing.push(byte);
}
}
if !new_line_flag {
break;
}
}
Some(Err(err)) => return Poll::Ready(Err(Error::IoError(err))),
None => break,
},
Poll::Pending => return Poll::Pending,
}
}
}
let res = match String::from_utf8(bytes) {
Ok(line) => Ok((line, trailing)),
Err(_) => Err(Error::InvalidUtf8InResponse),
};
Poll::Ready(res)
}
fn parse_status_line(line: &str) -> (i32, String) {
let mut status_code = String::with_capacity(3);
let mut reason_phrase = String::with_capacity(2);
let mut spaces = 0;
for c in line.chars() {
if spaces >= 2 {
reason_phrase.push(c);
}
if c == ' ' {
spaces += 1;
} else if spaces == 1 {
status_code.push(c);
}
}
if let Ok(status_code) = status_code.parse::<i32>() {
return (status_code, reason_phrase);
}
(503, "Server did not provide a status line".to_string())
}
fn parse_header(mut line: String) -> Option<(String, String)> {
if let Some(location) = line.find(':') {
let value = if let Some(sp) = line.get(location + 1..location + 2) {
if sp == " " {
line[location + 2..].to_string()
} else {
line[location + 1..].to_string()
}
} else {
line[location + 1..].to_string()
};
line.truncate(location);
line.make_ascii_lowercase();
return Some((line, value));
}
None
}