use core::fmt;
use std::time::Duration;
use http_net::{BodyPlan, Headers, Method, OutgoingBody, RequestHead, ResponseHeadRef, Version};
use crate::Finished;
use crate::client::{ClientInfo, IngressKind};
use crate::config::{Limits, Profile, ProxyKind};
use crate::engine::http1::engine_error;
use crate::engine::{EngineError, Family, HeadContext, Http1, OutBuf, RefusalKind, Service};
use crate::envelope::Envelope;
use crate::forwarded::{self, FieldLines, Lines, WalkError};
use crate::reply::Reply;
use crate::service::{HeadAction, Outcome, WorkClass};
pub type HttpReply = Reply<HttpFamily>;
#[derive(Clone, Copy, Debug, Default)]
pub struct HttpFamily;
#[derive(Clone, Copy)]
pub struct HttpHead<'a> {
raw: RequestHead<'a>,
client: ClientInfo,
authority: Option<&'a str>,
}
impl<'a> HttpHead<'a> {
#[must_use]
pub fn method(&self) -> Method {
self.raw.method()
}
#[must_use]
pub fn method_str(&self) -> &'a str {
self.raw.method_str()
}
#[must_use]
pub fn target(&self) -> &'a str {
self.raw.target()
}
#[must_use]
pub fn path(&self) -> &'a str {
self.raw.path()
}
#[must_use]
pub fn query(&self) -> Option<&'a str> {
self.raw.query()
}
#[must_use]
pub fn version(&self) -> Version {
self.raw.version()
}
#[must_use]
pub fn header(&self, name: &str) -> Option<&'a [u8]> {
self.raw.header(name)
}
#[must_use]
pub fn header_str(&self, name: &str) -> Option<&'a str> {
self.raw.header_str(name)
}
pub fn header_all<'n>(&self, name: &'n str) -> impl Iterator<Item = &'a [u8]> + use<'a, 'n> {
self.raw.header_all(name)
}
#[must_use]
pub fn headers(&self) -> Fields<'a> {
Fields {
inner: FieldSource::Head(self.raw.headers()),
}
}
#[must_use]
pub fn authority(&self) -> Option<&'a str> {
self.authority
}
#[must_use]
pub fn client(&self) -> &ClientInfo {
&self.client
}
#[must_use]
pub fn content_length(&self) -> Option<u64> {
self.raw.content_length()
}
#[must_use]
pub fn expects_continue(&self) -> bool {
self.raw.expects_continue()
}
#[must_use]
pub fn is_upgrade(&self) -> bool {
self.raw.is_upgrade()
}
}
impl fmt::Debug for HttpHead<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpHead")
.field("head", &self.raw)
.field("client", &self.client)
.finish()
}
}
#[derive(Clone)]
pub struct Fields<'a> {
inner: FieldSource<'a>,
}
#[derive(Clone)]
enum FieldSource<'a> {
Head(Headers<'a>),
Copy(&'a [u8], core::slice::Iter<'a, (Span, Span)>),
}
impl<'a> Iterator for Fields<'a> {
type Item = (&'a str, &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
match &mut self.inner {
FieldSource::Head(h) => h.next(),
FieldSource::Copy(bytes, spans) => {
let (name, value) = spans.next()?;
let name = core::str::from_utf8(name.of(bytes)).unwrap_or("");
Some((name, value.of(bytes)))
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.inner {
FieldSource::Head(h) => h.size_hint(),
FieldSource::Copy(_, spans) => spans.size_hint(),
}
}
}
impl ExactSizeIterator for Fields<'_> {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HttpBody<'a> {
Empty,
Full(&'a [u8]),
}
impl<'a> HttpBody<'a> {
#[must_use]
pub fn bytes(&self) -> &'a [u8] {
match self {
HttpBody::Empty => &[],
HttpBody::Full(b) => b,
}
}
}
#[derive(Clone, Copy)]
pub struct HttpRequest<'a> {
head: HttpHead<'a>,
body: &'a [u8],
}
impl<'a> HttpRequest<'a> {
#[must_use]
pub fn head(&self) -> &HttpHead<'a> {
&self.head
}
#[must_use]
pub fn method(&self) -> Method {
self.head.method()
}
#[must_use]
pub fn path(&self) -> &'a str {
self.head.path()
}
#[must_use]
pub fn query(&self) -> Option<&'a str> {
self.head.query()
}
#[must_use]
pub fn header(&self, name: &str) -> Option<&'a [u8]> {
self.head.header(name)
}
#[must_use]
pub fn client(&self) -> &ClientInfo {
self.head.client()
}
#[must_use]
pub fn body(&self) -> HttpBody<'a> {
if self.body.is_empty() {
HttpBody::Empty
} else {
HttpBody::Full(self.body)
}
}
#[must_use]
pub fn body_bytes(&self) -> &'a [u8] {
self.body
}
#[must_use]
pub fn detach_head(&self) -> DetachedHead {
DetachedHead::of(&self.head)
}
}
#[derive(Clone, Copy, Debug)]
struct Span {
start: u32,
len: u32,
}
impl Span {
fn of(self, bytes: &[u8]) -> &[u8] {
let start = self.start as usize;
bytes.get(start..start + self.len as usize).unwrap_or(&[])
}
}
pub struct DetachedHead {
bytes: Vec<u8>,
method: Method,
version: Version,
method_str: Span,
target: Span,
path: Span,
query: Option<Span>,
fields: Vec<(Span, Span)>,
}
const CREDENTIALS: [&str; 4] = [
"authorization",
"proxy-authorization",
"cookie",
"set-cookie",
];
impl DetachedHead {
fn of(head: &HttpHead<'_>) -> Self {
let mut bytes = Vec::with_capacity(512);
let mut put = |value: &[u8]| {
let start = bytes.len() as u32;
bytes.extend_from_slice(value);
Span {
start,
len: value.len() as u32,
}
};
let method_str = put(head.method_str().as_bytes());
let target = put(head.target().as_bytes());
let path = put(head.path().as_bytes());
let query = head.query().map(|q| put(q.as_bytes()));
let fields = head
.headers()
.map(|(name, value)| (put(name.as_bytes()), put(value)))
.collect();
DetachedHead {
bytes,
method: head.method(),
version: head.version(),
method_str,
target,
path,
query,
fields,
}
}
fn text(&self, span: Span) -> &str {
core::str::from_utf8(span.of(&self.bytes)).unwrap_or("")
}
#[must_use]
pub fn method(&self) -> Method {
self.method
}
#[must_use]
pub fn method_str(&self) -> &str {
self.text(self.method_str)
}
#[must_use]
pub fn target(&self) -> &str {
self.text(self.target)
}
#[must_use]
pub fn path(&self) -> &str {
self.text(self.path)
}
#[must_use]
pub fn query(&self) -> Option<&str> {
self.query.map(|q| self.text(q))
}
#[must_use]
pub fn version(&self) -> Version {
self.version
}
#[must_use]
pub fn header(&self, name: &str) -> Option<&[u8]> {
self.headers()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, value)| value)
}
pub fn header_all<'s>(&'s self, name: &'s str) -> impl Iterator<Item = &'s [u8]> + 's {
self.headers()
.filter(move |(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, value)| value)
}
#[must_use]
pub fn headers(&self) -> Fields<'_> {
Fields {
inner: FieldSource::Copy(&self.bytes, self.fields.iter()),
}
}
}
impl fmt::Debug for DetachedHead {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("DetachedHead");
d.field("method", &self.method_str())
.field("target", &self.target());
let fields: Vec<(&str, &str)> = self
.headers()
.map(|(n, v)| {
let secret = CREDENTIALS.iter().any(|c| n.eq_ignore_ascii_case(c));
let value = if secret {
"<redacted>"
} else {
core::str::from_utf8(v).unwrap_or("<binary>")
};
(n, value)
})
.collect();
d.field("fields", &fields).finish()
}
}
impl fmt::Debug for HttpRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpRequest")
.field("head", &self.head)
.field("body_len", &self.body.len())
.finish()
}
}
const MAX_FIELDS: usize = 64;
#[derive(Default)]
pub struct HttpParts {
status: u16,
names: Vec<u8>,
values: Vec<u8>,
spans: Vec<(u32, u32, u32, u32)>,
body: Vec<u8>,
body_static: Option<&'static [u8]>,
close: bool,
retry_after: Option<u64>,
invalid: bool,
}
impl HttpParts {
fn clear(&mut self, wipe: bool) {
if wipe {
crate::engine::wipe_bytes(&mut self.values);
crate::engine::wipe_bytes(&mut self.body);
}
self.status = 0;
self.names.clear();
self.values.clear();
self.spans.clear();
self.body.clear();
self.body_static = None;
self.close = false;
self.retry_after = None;
self.invalid = false;
}
}
impl fmt::Debug for HttpParts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names: Vec<&str> = self
.spans
.iter()
.map(|&(s, e, _, _)| {
core::str::from_utf8(&self.names[s as usize..e as usize]).unwrap_or("?")
})
.collect();
f.debug_struct("HttpParts")
.field("status", &self.status)
.field("fields", &names)
.field(
"body_len",
&self.body_static.map_or(self.body.len(), <[u8]>::len),
)
.finish()
}
}
impl Reply<HttpFamily> {
pub fn status(&mut self, status: u16) -> &mut Self {
self.parts.status = status;
self
}
pub fn header(&mut self, name: &str, value: impl AsRef<[u8]>) -> &mut Self {
let p = &mut self.parts;
if p.spans.len() >= MAX_FIELDS {
p.invalid = true;
return self;
}
let value = value.as_ref();
let (ns, vs) = (p.names.len() as u32, p.values.len() as u32);
p.names.extend_from_slice(name.as_bytes());
p.values.extend_from_slice(value);
p.spans
.push((ns, p.names.len() as u32, vs, p.values.len() as u32));
self
}
pub fn body(&mut self, bytes: &[u8]) -> &mut Self {
let p = &mut self.parts;
p.body.clear();
p.body.extend_from_slice(bytes);
p.body_static = None;
self
}
pub fn body_static(&mut self, bytes: &'static [u8]) -> &mut Self {
self.parts.body.clear();
self.parts.body_static = Some(bytes);
self
}
pub fn text(&mut self, status: u16, text: &str) -> &mut Self {
self.status(status)
.header("content-type", "text/plain; charset=utf-8")
.body(text.as_bytes())
}
pub fn html(&mut self, status: u16, html: &str) -> &mut Self {
self.status(status)
.header("content-type", "text/html; charset=utf-8")
.body(html.as_bytes())
}
pub fn close(&mut self) -> &mut Self {
self.parts.close = true;
self
}
pub fn retry_after(&mut self, after: Duration) -> &mut Self {
self.parts.retry_after = Some(after.as_secs().max(1));
self
}
}
impl<'h> FieldLines<'h> for RequestHead<'h> {
fn field_lines(&self, name: &str, out: &mut Lines<'h>) -> Result<(), WalkError> {
for v in self.header_all(name) {
out.push(v)?;
}
Ok(())
}
}
impl Family for HttpFamily {
type Engine = Http1;
type Head<'a> = HttpHead<'a>;
type Request<'a> = HttpRequest<'a>;
type Parts = HttpParts;
fn engine(&self, profile: &Profile, limits: &Limits) -> Http1 {
let engine_limits = limits
.engine
.with_max_body(limits.max_body.min(limits.engine.max_body()));
Http1::new(engine_limits, profile.wipe_buffers)
}
fn head<'a>(
&self,
raw: RequestHead<'a>,
cx: &HeadContext<'a>,
) -> Result<(HttpHead<'a>, ClientInfo), EngineError> {
let base = *cx.client;
let ingress_error = EngineError {
kind: RefusalKind::Ingress,
status: 400,
};
let Some(proxy) = cx.proxy else {
return Ok((
HttpHead {
raw,
client: base,
authority: raw.host(),
},
base,
));
};
match proxy.kind {
ProxyKind::Headers(source) => {
if let Some(client) = cx.resolved {
let host = if proxy.trust_host {
forwarded::trusted_host(source, &raw)
} else {
None
};
let authority = host.or_else(|| raw.host());
return Ok((
HttpHead {
raw,
client,
authority,
},
client,
));
}
let unix = base.ingress() == IngressKind::Unix;
let peer = match base.peer() {
Some(p) => Some(p),
None if unix => None,
None => return Err(ingress_error),
};
let rules = forwarded::Rules {
source,
trusted: &proxy.trusted,
max_hops: proxy.max_hops,
missing: proxy.missing,
unknown: proxy.unknown,
trust_scheme: proxy.trust_scheme,
trust_host: proxy.trust_host,
};
let found = forwarded::resolve_from(
peer.map(|p| p.ip()),
peer.map_or(0, |p| p.port()),
&rules,
&raw,
)
.map_err(|_| ingress_error)?;
let client = if found.forwarded {
base.behind_proxy(found.client, found.port, found.hops, IngressKind::Forwarded)
.with_scheme(found.scheme.unwrap_or(cx.declared_scheme))
} else {
base
};
let authority = found.host.or_else(|| raw.host());
Ok((
HttpHead {
raw,
client,
authority,
},
client,
))
}
ProxyKind::ProxyProtocol(_) => {
let client = base;
let tlv = if proxy.trust_host {
cx.proxy_facts
.and_then(|f| f.authority())
.filter(|a| forwarded::valid_authority(a.as_bytes()))
} else {
None
};
Ok((
HttpHead {
raw,
client,
authority: tlv.or_else(|| raw.host()),
},
client,
))
}
}
}
fn request<'a>(&self, head: HttpHead<'a>, body: &'a [u8]) -> HttpRequest<'a> {
HttpRequest { head, body }
}
fn new_parts(&self) -> HttpParts {
HttpParts::default()
}
fn clear_parts(&self, parts: &mut HttpParts, wipe: bool) {
parts.clear(wipe);
}
fn respond(
&self,
engine: &mut Http1,
parts: &mut HttpParts,
unix_secs: u64,
out: &mut OutBuf,
) -> Result<u16, EngineError> {
if parts.invalid {
return Err(EngineError {
kind: RefusalKind::Internal,
status: 500,
});
}
let status = if parts.status == 0 { 200 } else { parts.status };
let mut fields: [(&str, &[u8]); MAX_FIELDS + 1] = [("", &[]); MAX_FIELDS + 1];
let mut n = 0;
for &(ns, ne, vs, ve) in &parts.spans {
let Ok(name) = core::str::from_utf8(&parts.names[ns as usize..ne as usize]) else {
return Err(EngineError {
kind: RefusalKind::Internal,
status: 500,
});
};
fields[n] = (name, &parts.values[vs as usize..ve as usize]);
n += 1;
}
let mut digits = [0u8; 20];
if let Some(secs) = parts.retry_after {
fields[n] = ("retry-after", format_secs(secs, &mut digits));
n += 1;
}
let body: &[u8] = match parts.body_static {
Some(b) => b,
None => &parts.body,
};
let mut head = ResponseHeadRef::new(
status,
&fields[..n],
if body.is_empty() {
OutgoingBody::Empty
} else {
OutgoingBody::Full(body.len() as u64)
},
);
head.close = parts.close;
let date = engine.dates.update(unix_secs);
let start = out.len();
match engine.conn.write_head(&head, Some(date), out) {
Ok(BodyPlan::Raw) => {
out.push(body);
Ok(status)
}
Ok(_) => Ok(status),
Err(e) => {
out.bytes.truncate(start);
let mut err = engine_error(e);
err.kind = RefusalKind::Internal;
err.status = 500;
Err(err)
}
}
}
}
fn format_secs(mut n: u64, buf: &mut [u8; 20]) -> &[u8] {
let mut i = buf.len();
loop {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
if n == 0 {
break;
}
}
&buf[i..]
}
pub trait HttpService: Send + Sync {
const WORK: WorkClass = WorkClass::Quick;
fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction {
let _ = (head, env);
HeadAction::Proceed
}
fn call(&self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome;
fn done(&self, env: &Envelope, finished: &Finished) {
let _ = (env, finished);
}
}
impl<S: HttpService> Service<HttpFamily> for S {
const WORK: WorkClass = <S as HttpService>::WORK;
fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction {
HttpService::head(self, head, env)
}
fn call(&self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
HttpService::call(self, request, env, reply)
}
fn done(&self, env: &Envelope, finished: &Finished) {
HttpService::done(self, env, finished);
}
}
pub struct Handler<F> {
f: F,
}
pub fn http<A, F>(addr: A, handler: F) -> Result<crate::Running, crate::ServeError>
where
A: std::net::ToSocketAddrs,
F: Fn(&HttpRequest<'_>, &mut HttpReply) + Send + Sync + 'static,
{
tier1(addr, None, handler)
}
pub fn http_behind_proxy<A, F>(
addr: A,
trusted: &str,
handler: F,
) -> Result<crate::Running, crate::ServeError>
where
A: std::net::ToSocketAddrs,
F: Fn(&HttpRequest<'_>, &mut HttpReply) + Send + Sync + 'static,
{
use crate::error::{ConfigError, ConfigErrors, RuleId, Subject};
let trusted = crate::TrustedProxies::parse(trusted).map_err(|e| {
let error = match e {
crate::AddrError::Empty => ConfigError::new(
RuleId::TrustEmpty,
Subject::Listener("http-0".to_owned()),
"the trusted proxy list is empty",
"list the proxies' addresses, for example \"127.0.0.1, ::1\"",
),
_ => ConfigError::new(
RuleId::TrustParse,
Subject::Listener("http-0".to_owned()),
"the trusted proxy list does not parse",
"use addresses or CIDR blocks separated by commas, for example \"10.0.0.0/8\"",
),
};
crate::ServeError::Config(
ConfigErrors::from_vec(vec![error]).unwrap_or_else(|| unreachable!()),
)
})?;
tier1(addr, Some(trusted), handler)
}
fn tier1<A, F>(
addr: A,
trusted: Option<crate::TrustedProxies>,
handler: F,
) -> Result<crate::Running, crate::ServeError>
where
A: std::net::ToSocketAddrs,
F: Fn(&HttpRequest<'_>, &mut HttpReply) + Send + Sync + 'static,
{
let addrs: Vec<std::net::SocketAddr> = addr
.to_socket_addrs()
.map(Iterator::collect)
.unwrap_or_default();
let spec = |name: &str, a: &[std::net::SocketAddr]| {
let l = crate::ListenerSpec::tcp(name, a);
match &trusted {
None => l,
Some(t) => l.ingress(crate::Ingress::Proxy(crate::ProxySource::forwarded_for(
t.clone(),
))),
}
};
let mut builder = crate::Server::builder();
if addrs.is_empty() {
builder = builder.listener(spec("http-0", &[]));
}
for (i, a) in addrs.iter().enumerate() {
builder = builder.listener(spec(&format!("http-{i}"), core::slice::from_ref(a)));
}
builder.serve(Handler { f: handler })
}
pub fn handler<F>(f: F) -> Handler<F>
where
F: Fn(&HttpRequest<'_>, &mut HttpReply) + Send + Sync + 'static,
{
Handler { f }
}
impl<F> HttpService for Handler<F>
where
F: Fn(&HttpRequest<'_>, &mut HttpReply) + Send + Sync + 'static,
{
fn call(&self, request: &HttpRequest<'_>, _env: &Envelope, reply: &mut HttpReply) -> Outcome {
(self.f)(request, reply);
Outcome::Done
}
}
impl<F> fmt::Debug for Handler<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Handler")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reply_debug_redacts_values() {
let mut r = HttpReply::new(HttpParts::default());
r.header("set-cookie", "sid=secret").text(200, "x");
let text = format!("{r:?}");
assert!(!text.contains("secret"));
assert!(text.contains("set-cookie"));
}
#[test]
fn test_reply_too_many_fields_is_invalid() {
let mut r = HttpReply::new(HttpParts::default());
for _ in 0..=MAX_FIELDS {
r.header("x-a", "1");
}
assert!(r.parts.invalid);
}
#[test]
fn test_parts_clear_wipes() {
let mut r = HttpReply::new(HttpParts::default());
r.header("x", "secret").body(b"secret");
r.parts.clear(true);
assert!(r.parts.values.is_empty() && r.parts.body.is_empty() && r.parts.spans.is_empty());
}
}