use std::fmt;
use std::marker::PhantomData;
use http::{HeaderValue, StatusCode};
use crate::body::{BodyReader, BodyWriter};
use crate::util::ArrayVec;
use crate::CloseReason;
use amended::AmendedRequest;
mod amended;
#[cfg(test)]
mod test;
pub const MAX_RESPONSE_HEADERS: usize = 128;
pub mod state {
pub(crate) trait Named {
fn name() -> &'static str;
}
macro_rules! call_state {
($n:tt) => {
#[doc(hidden)]
pub struct $n(());
impl Named for $n {
fn name() -> &'static str {
stringify!($n)
}
}
};
}
call_state!(Prepare);
call_state!(SendRequest);
call_state!(Await100);
call_state!(SendBody);
call_state!(RecvResponse);
call_state!(RecvBody);
call_state!(Redirect);
call_state!(Cleanup);
}
use self::state::*;
pub struct Call<State> {
inner: Inner,
_ph: PhantomData<State>,
}
#[derive(Debug)]
pub(crate) struct Inner {
pub request: AmendedRequest,
pub analyzed: bool,
pub state: BodyState,
pub close_reason: ArrayVec<CloseReason, 4>,
pub force_recv_body: bool,
pub force_send_body: bool,
pub await_100_continue: bool,
pub status: Option<StatusCode>,
pub location: Option<HeaderValue>,
}
impl Inner {
fn is_redirect(&self) -> bool {
match self.status {
Some(v) => v.is_redirection() && v != StatusCode::NOT_MODIFIED,
None => false,
}
}
}
#[derive(Debug, Default)]
pub(crate) struct BodyState {
phase: RequestPhase,
writer: BodyWriter,
reader: Option<BodyReader>,
allow_non_standard_methods: bool,
stop_on_chunk_boundary: bool,
}
impl BodyState {
fn need_response_body(&self) -> bool {
!matches!(
self.reader,
Some(BodyReader::NoBody) | Some(BodyReader::LengthDelimited(0))
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
enum RequestPhase {
#[default]
Line,
Headers(usize),
Body,
}
impl RequestPhase {
fn is_prelude(&self) -> bool {
matches!(self, RequestPhase::Line | RequestPhase::Headers(_))
}
fn is_body(&self) -> bool {
matches!(self, RequestPhase::Body)
}
}
impl<S> Call<S> {
fn wrap(inner: Inner) -> Call<S>
where
S: Named,
{
let wrapped = Call {
inner,
_ph: PhantomData,
};
debug!("{:?}", wrapped);
wrapped
}
#[cfg(test)]
pub(crate) fn inner(&self) -> &Inner {
&self.inner
}
}
mod prepare;
mod sendreq;
pub enum SendRequestResult {
Await100(Call<Await100>),
SendBody(Call<SendBody>),
RecvResponse(Call<RecvResponse>),
}
mod await100;
pub enum Await100Result {
SendBody(Call<SendBody>),
RecvResponse(Call<RecvResponse>),
}
mod sendbody;
mod recvresp;
pub enum RecvResponseResult {
RecvBody(Call<RecvBody>),
Redirect(Call<Redirect>),
Cleanup(Call<Cleanup>),
}
mod recvbody;
pub enum RecvBodyResult {
Redirect(Call<Redirect>),
Cleanup(Call<Cleanup>),
}
mod redirect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RedirectAuthHeaders {
Never,
SameHost,
}
impl Call<Cleanup> {
pub fn must_close_connection(&self) -> bool {
self.close_reason().is_some()
}
pub fn close_reason(&self) -> Option<&'static str> {
self.inner.close_reason.first().map(|s| s.explain())
}
}
impl<State: Named> fmt::Debug for Call<State> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Call<{}>", State::name())
}
}
impl fmt::Debug for RequestPhase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Line => write!(f, "SendLine"),
Self::Headers(_) => write!(f, "SendHeaders"),
Self::Body => write!(f, "SendBody"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::amended::AmendedRequest;
use crate::client::state::SendRequest;
use crate::client::Inner;
use crate::Error;
use http::{header, Method, Request, Version};
use std::str;
#[test]
fn get_simple() {
let req = Request::get("http://foo.test/page")
.header("content-length", "0")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /page HTTP/1.1\r\n\
host: foo.test\r\n\
content-length: 0\r\n\
\r\n"
);
let Ok(Some(SendRequestResult::RecvResponse(_call))) = call.proceed() else {
panic!("Exepcted `RecvResponse`")
};
}
#[test]
fn head_simple() {
let req = Request::head("http://foo.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "HEAD /page HTTP/1.1\r\nhost: foo.test\r\n\r\n");
}
#[test]
fn head_without_body() {
let req = Request::head("http://foo.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::RecvResponse(_) = call else {
panic!("Expected RecvResponse")
};
}
#[test]
fn head_with_body_despite_method() {
let req = Request::head("http://foo.fest/page")
.header(header::TRANSFER_ENCODING, "chunked")
.body(())
.unwrap();
let mut call = Call::new(req).unwrap();
call.force_send_body();
let mut call = call.proceed();
let mut output = vec![0; 1024];
call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
let (i, n) = call.write(&[], &mut output).unwrap();
assert_eq!(i, 0);
assert_eq!(n, 5);
assert!(call.can_proceed());
}
#[test]
fn post_simple() {
let req = Request::post("http://f.test/page")
.header("content-length", 5)
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n1 = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
let (i1, n2) = call.write(b"hallo", &mut output[n1..]).unwrap();
assert_eq!(i1, 5);
let s = str::from_utf8(&output[..n1 + n2]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo"
);
}
#[test]
fn post_small_output() {
let req = Request::post("http://f.test/page")
.header("content-length", 5)
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let body = b"hallo";
{
let n = call.write(&mut output[..25]).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "POST /page HTTP/1.1\r\n");
assert!(!call.can_proceed());
}
{
let n = call.write(&mut output[..20]).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "host: f.test\r\n");
assert!(!call.can_proceed());
}
{
let n = call.write(&mut output[..21]).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "content-length: 5\r\n\r\n");
assert!(call.can_proceed());
}
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
{
let (i, n) = call.write(body, &mut output[..25]).unwrap();
assert_eq!(n, 5);
assert_eq!(i, 5);
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "hallo");
assert!(call.can_proceed());
}
}
#[test]
fn post_with_short_content_length() {
let req = Request::post("http://f.test/page")
.header("content-length", 2)
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n1 = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
let body = b"hallo";
let r = call.write(body, &mut output[n1..]);
assert_eq!(r.unwrap_err(), Error::BodyLargerThanContentLength);
let body = b"ha";
let r = call.write(body, &mut output[n1..]);
assert!(r.is_ok());
assert!(call.can_proceed());
}
#[test]
fn post_with_short_body_input() {
let req = Request::post("http://f.test/page")
.header("content-length", 5)
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n1 = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
let (i1, n2) = call.write(b"ha", &mut output[n1..]).unwrap();
assert_eq!(i1, 2);
let (i2, n3) = call.write(b"ha", &mut output[n1 + n2..]).unwrap();
assert_eq!(i2, 2);
let s = str::from_utf8(&output[..n1 + n2 + n3]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhaha"
);
assert!(!call.can_proceed());
let err = call.write(b"llo", &mut output[n1 + n2 + n3..]).unwrap_err();
assert_eq!(err, Error::BodyLargerThanContentLength);
let (i3, n4) = call.write(b"l", &mut output[n1 + n2 + n3..]).unwrap();
assert_eq!(i3, 1);
let s = str::from_utf8(&output[..n1 + n2 + n3 + n4]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhahal"
);
assert!(call.can_proceed());
}
#[test]
fn post_with_chunked() {
let req = Request::post("http://f.test/page")
.header("transfer-encoding", "chunked")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n1 = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody")
};
let body = b"hallo";
let (i1, n2) = call.write(body, &mut output[n1..]).unwrap();
assert_eq!(i1, 5);
let (i2, n3) = call.write(body, &mut output[n1 + n2..]).unwrap();
assert_eq!(i2, 5);
let (i3, n4) = call.write(&[], &mut output[n1 + n2 + n3..]).unwrap();
assert_eq!(i3, 0);
let s = str::from_utf8(&output[..n1 + n2 + n3 + n4]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ntransfer-encoding: chunked\r\n\r\n5\r\nhallo\r\n5\r\nhallo\r\n0\r\n\r\n"
);
assert!(call.can_proceed());
}
#[test]
fn post_without_body() {
let req = Request::post("http://foo.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody");
};
assert!(!call.can_proceed());
let (i, n) = call.write(&[], &mut output).unwrap();
assert_eq!(i, 0);
assert_eq!(n, 5);
assert!(call.can_proceed());
}
#[test]
fn post_streaming() {
let req = Request::post("http://f.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n1 = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody");
};
let (i2, n2) = call.write(b"hallo", &mut output[n1..]).unwrap();
let (i3, n3) = call.write(&[], &mut output[n1 + n2..]).unwrap();
let i1 = 0;
assert_eq!(i1, 0);
assert_eq!(i2, 5);
assert_eq!(n1, 65);
assert_eq!(n2, 10);
assert_eq!(i3, 0);
assert_eq!(n3, 5);
let s = str::from_utf8(&output[..(n1 + n2 + n3)]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ntransfer-encoding: chunked\r\n\r\n5\r\nhallo\r\n0\r\n\r\n"
);
}
#[test]
fn post_streaming_with_size() {
let req = Request::post("http://f.test/page")
.header("content-length", "5")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let headers_n = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody");
};
let (i1, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap();
assert_eq!(i1, 5); assert_eq!(n1, 5);
assert!(call.can_proceed());
let err = call
.write(b"hallo", &mut output[headers_n + n1..])
.unwrap_err();
assert_eq!(err, Error::BodyContentAfterFinish);
let s = str::from_utf8(&output[..headers_n + n1]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo"
);
}
#[test]
fn post_streaming_after_end() {
let req = Request::post("http://f.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let headers_n = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody");
};
let (_, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap();
let (_, n2) = call.write(&[], &mut output[headers_n + n1..]).unwrap();
let err = call.write(b"after end", &mut output[headers_n + n1 + n2..]);
assert_eq!(err, Err(Error::BodyContentAfterFinish));
}
#[test]
fn post_streaming_too_much() {
let req = Request::post("http://f.test/page")
.header("content-length", "5")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let headers_n = call.write(&mut output).unwrap();
assert!(call.can_proceed());
let call = call.proceed().unwrap().unwrap();
let SendRequestResult::SendBody(mut call) = call else {
panic!("Expected SendBody");
};
let (i1, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap();
assert_eq!(i1, 5); assert_eq!(n1, 5);
assert!(call.can_proceed());
let err = call
.write(b"hallo", &mut output[headers_n + n1..])
.unwrap_err();
assert_eq!(err, Error::BodyContentAfterFinish);
let s = str::from_utf8(&output[..headers_n + n1]).unwrap();
assert_eq!(
s,
"POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo"
);
}
#[test]
fn username_password_uri() {
let req = Request::get("http://martin:secret@f.test/page")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /page HTTP/1.1\r\nhost: f.test\r\n\
authorization: Basic bWFydGluOnNlY3JldA==\r\n\r\n"
);
}
#[test]
fn username_uri() {
let req = Request::get("http://martin@f.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /page HTTP/1.1\r\nhost: f.test\r\n\
authorization: Basic bWFydGluOg==\r\n\r\n"
);
}
#[test]
fn password_uri() {
let req = Request::get("http://:secret@f.test/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /page HTTP/1.1\r\nhost: f.test\r\n\
authorization: Basic OnNlY3JldA==\r\n\r\n"
);
}
#[test]
fn override_auth_header() {
let req = Request::get("http://martin:secret@f.test/page")
.header("authorization", "meh meh meh")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /page HTTP/1.1\r\nhost: f.test\r\n\
authorization: meh meh meh\r\n\r\n"
);
}
#[test]
fn non_standard_port() {
let req = Request::get("http://f.test:8080/page").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "GET /page HTTP/1.1\r\nhost: f.test:8080\r\n\r\n");
}
#[test]
fn non_standard_method_not_allowed() {
let m = Method::from_bytes(b"FNORD").unwrap();
let req = Request::builder()
.method(m.clone())
.uri("http://f.test:8080/page")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let err = call.write(&mut output).unwrap_err();
assert_eq!(err, Error::MethodVersionMismatch(m, Version::HTTP_11));
}
#[test]
fn non_standard_method_when_allowed() {
let m = Method::from_bytes(b"FNORD").unwrap();
let req = Request::builder()
.method(m.clone())
.uri("http://f.test:8080/page")
.body(())
.unwrap();
let mut call = Call::new(req).unwrap();
call.allow_non_standard_methods(true);
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(s, "FNORD /page HTTP/1.1\r\nhost: f.test:8080\r\n\r\n");
}
#[test]
fn ensure_reasonable_stack_sizes() {
macro_rules! ensure {
($type:ty, $size:tt) => {
let sz = std::mem::size_of::<$type>();
assert!(
sz <= $size,
"Stack size of {} is too big {} > {}",
stringify!($type),
sz,
$size
);
};
}
ensure!(http::Request<()>, 300); ensure!(AmendedRequest, 400); ensure!(Inner, 600); ensure!(Call<SendRequest>, 600); }
#[test]
fn connect() {
let req = Request::builder()
.method(Method::CONNECT)
.uri("http://example.com:80")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\n\r\n"
);
let SendRequestResult::RecvResponse(mut call) = call.proceed().unwrap().unwrap() else {
panic!("Expected RecvResponse state")
};
let input = b"HTTP/1.1 200 OK\r\n\r\n";
let (n, res) = call.try_response(input, false).unwrap();
assert_eq!(n, 19);
let Some(res) = res else {
panic!("`try_response()` should return a response");
};
assert!(res.headers().is_empty());
let RecvResponseResult::Cleanup(_call) = call.proceed().unwrap() else {
panic!("Expected Cleanup state")
};
}
#[test]
fn connect_read_body() {
let req = Request::builder()
.method(Method::CONNECT)
.uri("http://example.com:80")
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\n\r\n"
);
let SendRequestResult::RecvResponse(mut call) = call.proceed().unwrap().unwrap() else {
panic!("Expected RecvResponse state")
};
call.force_recv_body();
let input = b"HTTP/1.1 200 OK\r\ncontent-length: 1024\r\n\r\n";
let (_n, res) = call.try_response(input, false).unwrap();
let Some(_res) = res else {
panic!("`try_response()` should return a response");
};
let RecvResponseResult::RecvBody(_call) = call.proceed().unwrap() else {
panic!("Expect RecvBody state");
};
}
#[test]
fn connect_send_body_fails() {
let req = Request::builder()
.method(Method::CONNECT)
.uri("http://example.com:80")
.header("content-length", 1024)
.body(())
.unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
call.write(&mut output)
.expect_err("no body allowed on CONNECT request");
}
#[test]
fn connect_send_body_with_footgun() {
let req = Request::builder()
.method(Method::CONNECT)
.uri("http://example.com:80")
.header("content-length", 1024)
.body(())
.unwrap();
let mut call = Call::new(req).unwrap();
call.force_send_body();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\ncontent-length: 1024\r\n\r\n"
);
let SendRequestResult::SendBody(_call) = call.proceed().unwrap().unwrap() else {
panic!("Expected SendBody state")
};
}
#[test]
fn query_no_slash() {
let req = Request::get("http://foo.test?query=foo").body(()).unwrap();
let call = Call::new(req).unwrap();
let mut call = call.proceed();
let mut output = vec![0; 1024];
let n = call.write(&mut output).unwrap();
let s = str::from_utf8(&output[..n]).unwrap();
assert_eq!(
s,
"GET /?query=foo HTTP/1.1\r\n\
host: foo.test\r\n\
\r\n"
);
}
}