sark-http 0.27.0

HTTP wire types, semantics, codecs, and ownership invariants for Sark
Documentation
use sark_http::{
    field::{
        self, head,
        metadata::{
            block::{self, Connect, Gate, Kind, Validation, Version},
            map::record::Span,
        },
    },
    payload::response::{
        Method,
        headers::{Entry, Error, Name, head::Template},
    },
};

const fn entry<'a>(name: &'a [u8], value: &'a [u8]) -> Result<Entry<'a>, Error> {
    let name = match Name::try_bytes(name) {
        Ok(name) => name,
        Err(error) => return Err(error),
    };
    let value = match field::Value::try_bytes(value) {
        Ok(value) => value,
        Err(error) => return Err(Error::Syntax(error)),
    };
    Ok(Entry::new(name, value))
}

pub const OPAQUE: &[u8] = b"\x80\xff\tready";
pub const REPEATED: &[u8] = b"HTTP/1.1 200 OK\r\nSet-Cookie: a=1\r\nSet-Cookie: \x80\xff\tready\r\nContent-Length: 0\r\n\r\n";
pub const TRAILERS: &[u8] = b"HTTP/1.1 200 OK\r\nx-head: head\r\ntransfer-encoding: chunked\r\n\r\n0\r\nX-Tail: \x80\xff\tready\r\n\r\n";
pub const STATUS: &[u8] = b"HTTP/1.1 599 High\r\nContent-Length: 0\r\n\r\n";
pub const EOF_BODY: &[u8] = b"HTTP/1.1 200 OK\r\n\r\nbody";

pub const ASTERISK: &[(head::Name, &[u8])] = &[
    (head::Name::Path, b"*"),
    (head::Name::Scheme, b"https"),
    (head::Name::Authority, b"example.com"),
];

pub const ASTERISK_METHODS: [(Kind, &[u8], Validation); 5] = [
    (Kind::Request, b"OPTIONS", Validation::Valid),
    (Kind::Request, b"GET", Validation::Malformed),
    (Kind::PushPromise, b"GET", Validation::Malformed),
    (Kind::PushPromise, b"HEAD", Validation::Malformed),
    (Kind::Request, b"CONNECT", Validation::Malformed),
];

pub const METHODS: [(&[u8], Method); 4] = [
    (b"HEAD", Method::Head),
    (b"head", Method::Other),
    (b"OPTIONS", Method::Other),
    (b"GET", Method::Other),
];

pub const TARGET: &[(head::Name, &[u8])] = &[
    (head::Name::Path, b"/"),
    (head::Name::Scheme, b"https"),
    (head::Name::Authority, b"example.com"),
];

pub const STANDARD: [&str; 83] = [
    "accept",
    "accept-charset",
    "accept-encoding",
    "accept-language",
    "accept-ranges",
    "access-control-allow-credentials",
    "access-control-allow-headers",
    "access-control-allow-methods",
    "access-control-allow-origin",
    "access-control-expose-headers",
    "access-control-max-age",
    "access-control-request-headers",
    "access-control-request-method",
    "age",
    "allow",
    "alt-svc",
    "authorization",
    "cache-control",
    "cache-status",
    "cdn-cache-control",
    "connection",
    "content-disposition",
    "content-encoding",
    "content-language",
    "content-length",
    "content-location",
    "content-range",
    "content-security-policy",
    "content-security-policy-report-only",
    "content-type",
    "cookie",
    "date",
    "dnt",
    "etag",
    "expect",
    "expires",
    "forwarded",
    "from",
    "host",
    "if-match",
    "if-modified-since",
    "if-none-match",
    "if-range",
    "if-unmodified-since",
    "keep-alive",
    "last-modified",
    "link",
    "location",
    "max-forwards",
    "origin",
    "pragma",
    "proxy-authenticate",
    "proxy-authorization",
    "proxy-connection",
    "public-key-pins",
    "public-key-pins-report-only",
    "range",
    "referer",
    "referrer-policy",
    "refresh",
    "retry-after",
    "sec-websocket-accept",
    "sec-websocket-extensions",
    "sec-websocket-key",
    "sec-websocket-protocol",
    "sec-websocket-version",
    "server",
    "set-cookie",
    "strict-transport-security",
    "te",
    "trailer",
    "transfer-encoding",
    "upgrade",
    "upgrade-insecure-requests",
    "user-agent",
    "vary",
    "via",
    "warning",
    "www-authenticate",
    "x-content-type-options",
    "x-dns-prefetch-control",
    "x-frame-options",
    "x-xss-protection",
];

pub const PRIVATE: &str = "X-Private";

const STATIC_FIELDS: &[Entry<'static>] = &[
    field::literal!(entry(b"content-type", b"text/plain")),
    field::literal!(entry(b"x-static", b"yes")),
];
pub const STATIC: Template = Template::new(STATIC_FIELDS);
pub const EMPTY: Template = Template::new(&[]);
const SEMANTIC_FIELDS: &[Entry<'static>] = &[
    field::literal!(entry(b"x-first", b"one")),
    field::literal!(entry(b"x-second", b" two")),
];
pub const SEMANTIC: Template = Template::new(SEMANTIC_FIELDS);

pub trait Admit: Sized {
    fn h3(connect: Connect, fields: &[(head::Name, &[u8])]) -> (Option<block::Plan>, Validation);
    fn fields(self, fields: &[(head::Name, &[u8])]) -> (Option<block::Plan>, Validation);
    fn response(version: Version, status: Span<'_>) -> block::Outcome;
}

impl Admit for Gate {
    fn h3(connect: Connect, fields: &[(head::Name, &[u8])]) -> (Option<block::Plan>, Validation) {
        Self::new(Kind::Request, connect, Version::Three).fields(fields)
    }

    fn fields(mut self, fields: &[(head::Name, &[u8])]) -> (Option<block::Plan>, Validation) {
        for (name, value) in fields {
            let check = self.accept_name(*name);
            if check != Validation::Valid {
                return (None, check);
            }
            let check = self.accept_value(*name, Span::from_slice(value));
            if check != Validation::Valid {
                return (None, check);
            }
        }
        self.finish().into_parts()
    }

    fn response(version: Version, status: Span<'_>) -> block::Outcome {
        let mut gate = Self::new(Kind::Response, Connect::Disabled, version);
        gate.accept_name(head::Name::Status);
        gate.accept_value(head::Name::Status, status);
        gate.finish()
    }
}