use alloc::vec::Vec;
use crate::{date::HttpDate, error::HttpError, message::Response, server::ServerConn};
pub trait Output {
fn put(&mut self, bytes: &[u8]);
fn reserve(&mut self, additional: usize) {
let _ = additional;
}
}
impl Output for Vec<u8> {
#[inline]
fn put(&mut self, bytes: &[u8]) {
self.extend_from_slice(bytes);
}
#[inline]
fn reserve(&mut self, additional: usize) {
Vec::reserve(self, additional);
}
}
impl<O: Output + ?Sized> Output for &mut O {
#[inline]
fn put(&mut self, bytes: &[u8]) {
(**self).put(bytes);
}
#[inline]
fn reserve(&mut self, additional: usize) {
(**self).reserve(additional);
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OutgoingBody {
Empty,
Full(u64),
Length(u64),
Chunked,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BodyPlan {
None,
Raw,
Stream,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ResponseHeadRef<'a> {
pub status: u16,
pub fields: &'a [(&'a str, &'a [u8])],
pub body: OutgoingBody,
pub close: bool,
pub upgrade: Option<&'a str>,
}
impl<'a> ResponseHeadRef<'a> {
#[must_use]
pub const fn new(status: u16, fields: &'a [(&'a str, &'a [u8])], body: OutgoingBody) -> Self {
Self {
status,
fields,
body,
close: false,
upgrade: None,
}
}
}
impl ServerConn {
pub fn write_head<O: Output + ?Sized>(
&mut self,
head: &ResponseHeadRef<'_>,
date: Option<&HttpDate>,
out: &mut O,
) -> Result<BodyPlan, HttpError> {
self.start_response(head, date.map(HttpDate::as_bytes).map(|d| &d[..]), out)
}
pub fn respond_into<O: Output + ?Sized>(
&mut self,
response: &Response,
date: Option<&HttpDate>,
out: &mut O,
) -> Result<(), HttpError> {
self.respond_dated(response, date.map(|d| &d.as_bytes()[..]), out)
}
pub fn send_body_into<O: Output + ?Sized>(
&mut self,
data: &[u8],
out: &mut O,
) -> Result<(), HttpError> {
self.stream_body(data, out)
}
pub fn end_body_into<O: Output + ?Sized>(
&mut self,
trailers: &[(&str, &[u8])],
out: &mut O,
) -> Result<(), HttpError> {
self.finish_body(trailers, out)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::{HttpError, ServerEvent};
fn conn_after(request: &[u8]) -> ServerConn {
let mut conn = ServerConn::new();
let _ = conn.feed(request);
loop {
match conn.poll() {
Ok(ServerEvent::RequestEnd) | Err(_) => return conn,
Ok(_) => {}
}
}
}
const GET: &[u8] = b"GET / HTTP/1.1\r\nHost: a\r\n\r\n";
#[test]
fn test_write_head_full_body_plans_raw_and_completes() {
let mut conn = conn_after(GET);
let mut out = Vec::new();
let head = ResponseHeadRef::new(200, &[("x", b"1")], OutgoingBody::Full(3));
assert_eq!(conn.write_head(&head, None, &mut out), Ok(BodyPlan::Raw));
assert_eq!(out, b"HTTP/1.1 200 OK\r\ncontent-length: 3\r\nx: 1\r\n\r\n");
assert_eq!(conn.requests_served(), 1);
assert!(matches!(conn.poll(), Ok(ServerEvent::NeedInput)));
}
#[test]
fn test_write_head_head_request_and_bodiless_plan_none() {
let mut conn = conn_after(b"HEAD / HTTP/1.1\r\nHost: a\r\n\r\n");
let mut out = Vec::new();
let head = ResponseHeadRef::new(200, &[], OutgoingBody::Full(9));
assert_eq!(conn.write_head(&head, None, &mut out), Ok(BodyPlan::None));
assert!(out.ends_with(b"content-length: 9\r\n\r\n"));
let mut conn = conn_after(GET);
let mut out = Vec::new();
let head = ResponseHeadRef::new(204, &[], OutgoingBody::Empty);
assert_eq!(conn.write_head(&head, None, &mut out), Ok(BodyPlan::None));
assert_eq!(out, b"HTTP/1.1 204 No Content\r\n\r\n");
let mut conn = conn_after(GET);
let head = ResponseHeadRef::new(304, &[], OutgoingBody::Full(1));
assert_eq!(
conn.write_head(&head, None, &mut Vec::new()),
Err(HttpError::BodyNotAllowed)
);
}
#[test]
fn test_write_head_refuses_bad_fields_and_writes_nothing() {
for (fields, err) in [
(
&[("content-length", &b"5"[..])][..],
HttpError::ReservedHeader,
),
(&[("bad name", &b"x"[..])][..], HttpError::InvalidHeaderName),
(&[("x", &b"a\r\nb"[..])][..], HttpError::BareCr),
(&[("x", &b" pad"[..])][..], HttpError::InvalidHeaderValue),
(&[("te", &b"trailers"[..])][..], HttpError::ReservedHeader),
] {
let mut conn = conn_after(GET);
let mut out = Vec::new();
let head = ResponseHeadRef::new(200, fields, OutgoingBody::Empty);
assert_eq!(
conn.write_head(&head, None, &mut out),
Err(err),
"{fields:?}"
);
assert!(out.is_empty());
conn.respond(&Response::new(204), &mut out).unwrap();
}
}
#[test]
fn test_write_head_upgrade_rules() {
let upgrade_req =
b"GET / HTTP/1.1\r\nHost: a\r\nConnection: upgrade\r\nUpgrade: websocket\r\n\r\n";
let mut conn = conn_after(upgrade_req);
let mut out = Vec::new();
let mut head = ResponseHeadRef::new(101, &[], OutgoingBody::Empty);
assert!(matches!(
conn.write_head(&head, None, &mut out),
Err(HttpError::InvalidState(_))
));
head.upgrade = Some("irc");
assert_eq!(
conn.write_head(&head, None, &mut out),
Err(HttpError::UpgradeNotOffered)
);
head.upgrade = Some("h2c");
assert_eq!(
conn.write_head(&head, None, &mut out),
Err(HttpError::UpgradeNotOffered)
);
head.upgrade = Some("a b");
assert_eq!(
conn.write_head(&head, None, &mut out),
Err(HttpError::InvalidHeaderValue)
);
let mut on_200 = ResponseHeadRef::new(200, &[], OutgoingBody::Empty);
on_200.upgrade = Some("websocket");
assert!(matches!(
conn.write_head(&on_200, None, &mut out),
Err(HttpError::InvalidState(_))
));
assert!(out.is_empty());
head.upgrade = Some("WebSocket");
assert_eq!(conn.write_head(&head, None, &mut out), Ok(BodyPlan::None));
assert_eq!(
out,
b"HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: WebSocket\r\n\r\n"
);
assert!(conn.is_upgraded());
}
#[test]
fn test_write_head_date_unless_set() {
let date = HttpDate::from_unix_seconds(0);
let mut conn = conn_after(GET);
let mut out = Vec::new();
let head = ResponseHeadRef::new(204, &[], OutgoingBody::Empty);
assert_eq!(
conn.write_head(&head, Some(&date), &mut out),
Ok(BodyPlan::None)
);
assert!(
out.windows(35)
.any(|w| w == b"date: Thu, 01 Jan 1970 00:00:00 GMT")
);
let mut conn = conn_after(GET);
let mut out = Vec::new();
let fields: &[(&str, &[u8])] = &[("Date", b"x")];
let head = ResponseHeadRef::new(204, fields, OutgoingBody::Empty);
assert_eq!(
conn.write_head(&head, Some(&date), &mut out),
Ok(BodyPlan::None)
);
assert!(!out.windows(4).any(|w| w == b"1970"));
}
#[test]
fn test_streaming_into_output_and_state_errors() {
let mut conn = conn_after(GET);
let mut out = Vec::new();
assert!(matches!(
conn.send_body_into(b"x", &mut out),
Err(HttpError::InvalidState(_))
));
let head = ResponseHeadRef::new(200, &[], OutgoingBody::Length(3));
assert_eq!(conn.write_head(&head, None, &mut out), Ok(BodyPlan::Stream));
assert!(matches!(
conn.write_head(&head, None, &mut out),
Err(HttpError::InvalidState(_))
));
conn.send_body_into(b"ab", &mut out).unwrap();
assert_eq!(
conn.end_body_into(&[], &mut out),
Err(HttpError::BodyLengthMismatch)
);
conn.send_body_into(b"c", &mut out).unwrap();
assert_eq!(
conn.end_body_into(&[("x", b"1")], &mut out),
Err(HttpError::TrailerNotAllowed)
);
conn.end_body_into(&[], &mut out).unwrap();
assert!(out.ends_with(b"\r\n\r\nabc"));
}
#[test]
fn test_output_by_reference_and_reserve_hint() {
let mut v = vec![1u8];
{
let mut r: &mut Vec<u8> = &mut v;
Output::reserve(&mut r, 64);
r.put(b"23");
}
assert_eq!(v, b"\x0123");
assert!(v.capacity() >= 3);
}
}