use std::fmt;
pub trait HeaderName<'name>: Copy + private::Sealed {
fn as_header_bytes(self) -> &'name [u8];
fn as_header_str(self) -> &'name str;
#[inline]
fn known_index(self) -> Option<usize> {
None
}
}
mod private {
pub trait Sealed {}
impl Sealed for &str {}
impl Sealed for super::RequestHeader {}
impl Sealed for super::ResponseHeader {}
}
impl<'name> HeaderName<'name> for &'name str {
#[inline]
fn as_header_bytes(self) -> &'name [u8] {
self.as_bytes()
}
#[inline]
fn as_header_str(self) -> &'name str {
self
}
}
impl<'name> HeaderName<'name> for RequestHeader {
#[inline]
fn as_header_bytes(self) -> &'name [u8] {
self.as_bytes()
}
#[inline]
fn as_header_str(self) -> &'name str {
self.as_str()
}
#[inline]
fn known_index(self) -> Option<usize> {
Some(self as usize)
}
}
impl<'name> HeaderName<'name> for ResponseHeader {
#[inline]
fn as_header_bytes(self) -> &'name [u8] {
self.as_bytes()
}
#[inline]
fn as_header_str(self) -> &'name str {
self.as_str()
}
#[inline]
fn known_index(self) -> Option<usize> {
Some(self as usize)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Header<'a> {
name: &'a [u8],
value: &'a [u8],
}
impl<'a> Header<'a> {
pub(crate) const EMPTY: Self = Self {
name: b"",
value: b"",
};
#[inline]
pub(crate) const fn new(name: &'a [u8], value: &'a [u8]) -> Self {
Self { name, value }
}
#[inline]
#[must_use]
pub const fn name(&self) -> &'a [u8] {
self.name
}
#[inline]
#[must_use]
pub const fn value(&self) -> &'a [u8] {
self.value
}
#[inline]
pub const fn name_str(&self) -> Result<&'a str, std::str::Utf8Error> {
std::str::from_utf8(self.name)
}
#[inline]
pub const fn value_str(&self) -> Result<&'a str, std::str::Utf8Error> {
std::str::from_utf8(self.value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Patch,
Options,
}
impl Method {
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Head => "HEAD",
Self::Post => "POST",
Self::Put => "PUT",
Self::Delete => "DELETE",
Self::Patch => "PATCH",
Self::Options => "OPTIONS",
}
}
#[inline]
#[must_use]
pub const fn can_have_body(self) -> bool {
!matches!(self, Self::Get | Self::Head)
}
#[inline]
pub(crate) const fn from_bytes(bytes: &[u8]) -> Option<Self> {
match bytes {
b"GET" => Some(Self::Get),
b"HEAD" => Some(Self::Head),
b"POST" => Some(Self::Post),
b"PUT" => Some(Self::Put),
b"DELETE" => Some(Self::Delete),
b"PATCH" => Some(Self::Patch),
b"OPTIONS" => Some(Self::Options),
_ => None,
}
}
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum HttpVersion {
Http10,
Http11,
}
impl HttpVersion {
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Http10 => "HTTP/1.0",
Self::Http11 => "HTTP/1.1",
}
}
#[inline]
pub(crate) const fn from_bytes(bytes: &[u8]) -> Option<Self> {
match bytes {
b"HTTP/1.0" => Some(Self::Http10),
b"HTTP/1.1" => Some(Self::Http11),
_ => None,
}
}
}
impl fmt::Display for HttpVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RequestHeader {
Host,
Accept,
AcceptEncoding,
AcceptLanguage,
Authorization,
CacheControl,
Connection,
ContentType,
ContentLength,
Cookie,
IfNoneMatch,
IfModifiedSince,
Origin,
Referer,
UserAgent,
TransferEncoding,
Upgrade,
UpgradeInsecureRequests,
}
impl RequestHeader {
pub(crate) const COUNT: usize = 18;
const _ASSERT_COUNT: () = assert!(Self::UpgradeInsecureRequests as usize + 1 == Self::COUNT);
#[inline]
const fn fold32(v: u32) -> u32 {
v | 0x2020_2020
}
#[inline]
const fn fold64(v: u64) -> u64 {
v | 0x2020_2020_2020_2020
}
#[inline]
const fn load32(bytes: &[u8], off: usize) -> u32 {
(bytes[off] as u32)
| (bytes[off + 1] as u32) << 8
| (bytes[off + 2] as u32) << 16
| (bytes[off + 3] as u32) << 24
}
#[inline]
const fn load64(bytes: &[u8], off: usize) -> u64 {
(bytes[off] as u64)
| (bytes[off + 1] as u64) << 8
| (bytes[off + 2] as u64) << 16
| (bytes[off + 3] as u64) << 24
| (bytes[off + 4] as u64) << 32
| (bytes[off + 5] as u64) << 40
| (bytes[off + 6] as u64) << 48
| (bytes[off + 7] as u64) << 56
}
#[inline]
const fn const32(b: &[u8]) -> u32 {
(b[0] as u32) | (b[1] as u32) << 8 | (b[2] as u32) << 16 | (b[3] as u32) << 24
}
#[inline]
const fn const64(b: &[u8]) -> u64 {
(b[0] as u64)
| (b[1] as u64) << 8
| (b[2] as u64) << 16
| (b[3] as u64) << 24
| (b[4] as u64) << 32
| (b[5] as u64) << 40
| (b[6] as u64) << 48
| (b[7] as u64) << 56
}
#[inline]
const fn eq4(input: &[u8], off: usize, expected: &[u8]) -> bool {
Self::fold32(Self::load32(input, off)) == Self::const32(expected)
}
#[inline]
const fn eq6(input: &[u8], expected: &[u8]) -> bool {
Self::eq4(
input,
0,
&[expected[0], expected[1], expected[2], expected[3]],
) && (input[4] | 0x20) == expected[4]
&& (input[5] | 0x20) == expected[5]
}
#[inline]
const fn eq7(input: &[u8], expected: &[u8]) -> bool {
Self::eq4(
input,
0,
&[expected[0], expected[1], expected[2], expected[3]],
) && (input[4] | 0x20) == expected[4]
&& (input[5] | 0x20) == expected[5]
&& (input[6] | 0x20) == expected[6]
}
#[inline]
const fn eq8(input: &[u8], off: usize, expected: &[u8]) -> bool {
Self::fold64(Self::load64(input, off)) == Self::const64(expected)
}
#[inline]
const fn eq10(input: &[u8], expected: &[u8]) -> bool {
Self::eq8(
input,
0,
&[
expected[0],
expected[1],
expected[2],
expected[3],
expected[4],
expected[5],
expected[6],
expected[7],
],
) && (input[8] | 0x20) == expected[8]
&& (input[9] | 0x20) == expected[9]
}
#[inline]
const fn eq12(input: &[u8], expected: &[u8]) -> bool {
Self::eq8(
input,
0,
&[
expected[0],
expected[1],
expected[2],
expected[3],
expected[4],
expected[5],
expected[6],
expected[7],
],
) && Self::eq4(
input,
8,
&[expected[8], expected[9], expected[10], expected[11]],
)
}
#[inline]
const fn eq13(input: &[u8], expected: &[u8]) -> bool {
Self::eq12(input, expected) && (input[12] | 0x20) == expected[12]
}
#[inline]
const fn eq14(input: &[u8], expected: &[u8]) -> bool {
Self::eq12(input, expected)
&& (input[12] | 0x20) == expected[12]
&& (input[13] | 0x20) == expected[13]
}
#[inline]
const fn eq15(input: &[u8], expected: &[u8]) -> bool {
Self::eq8(
input,
0,
&[
expected[0],
expected[1],
expected[2],
expected[3],
expected[4],
expected[5],
expected[6],
expected[7],
],
) && Self::eq8(
input,
7,
&[
expected[7],
expected[8],
expected[9],
expected[10],
expected[11],
expected[12],
expected[13],
expected[14],
],
)
}
#[inline]
const fn eq17(input: &[u8], expected: &[u8]) -> bool {
Self::eq8(
input,
0,
&[
expected[0],
expected[1],
expected[2],
expected[3],
expected[4],
expected[5],
expected[6],
expected[7],
],
) && Self::eq8(
input,
8,
&[
expected[8],
expected[9],
expected[10],
expected[11],
expected[12],
expected[13],
expected[14],
expected[15],
],
) && (input[16] | 0x20) == expected[16]
}
#[inline]
const fn eq25(input: &[u8], expected: &[u8]) -> bool {
Self::eq8(
input,
0,
&[
expected[0],
expected[1],
expected[2],
expected[3],
expected[4],
expected[5],
expected[6],
expected[7],
],
) && Self::eq8(
input,
8,
&[
expected[8],
expected[9],
expected[10],
expected[11],
expected[12],
expected[13],
expected[14],
expected[15],
],
) && Self::eq8(
input,
16,
&[
expected[16],
expected[17],
expected[18],
expected[19],
expected[20],
expected[21],
expected[22],
expected[23],
],
) && (input[24] | 0x20) == expected[24]
}
#[inline]
pub(crate) const fn from_bytes_ignore_case(bytes: &[u8]) -> Option<Self> {
match bytes.len() {
4 => {
if Self::eq4(bytes, 0, b"host") {
return Some(Self::Host);
}
None
}
6 => match bytes[0] | 0x20 {
b'a' if Self::eq6(bytes, b"accept") => Some(Self::Accept),
b'c' if Self::eq6(bytes, b"cookie") => Some(Self::Cookie),
b'o' if Self::eq6(bytes, b"origin") => Some(Self::Origin),
_ => None,
},
7 => match bytes[0] | 0x20 {
b'r' if Self::eq7(bytes, b"referer") => Some(Self::Referer),
b'u' if Self::eq7(bytes, b"upgrade") => Some(Self::Upgrade),
_ => None,
},
10 => match bytes[0] | 0x20 {
b'c' if Self::eq10(bytes, b"connection") => Some(Self::Connection),
b'u' if Self::eq10(bytes, b"user-agent") => Some(Self::UserAgent),
_ => None,
},
12 => {
if Self::eq12(bytes, b"content-type") {
return Some(Self::ContentType);
}
None
}
13 => match bytes[0] | 0x20 {
b'a' if Self::eq13(bytes, b"authorization") => Some(Self::Authorization),
b'c' if Self::eq13(bytes, b"cache-control") => Some(Self::CacheControl),
b'i' if Self::eq13(bytes, b"if-none-match") => Some(Self::IfNoneMatch),
_ => None,
},
14 => {
if Self::eq14(bytes, b"content-length") {
return Some(Self::ContentLength);
}
None
}
15 => {
if bytes[0] | 0x20 != b'a' {
return None;
}
match bytes[7] | 0x20 {
b'e' if Self::eq15(bytes, b"accept-encoding") => Some(Self::AcceptEncoding),
b'l' if Self::eq15(bytes, b"accept-language") => Some(Self::AcceptLanguage),
_ => None,
}
}
17 => match bytes[0] | 0x20 {
b'i' if Self::eq17(bytes, b"if-modified-since") => Some(Self::IfModifiedSince),
b't' if Self::eq17(bytes, b"transfer-encoding") => Some(Self::TransferEncoding),
_ => None,
},
25 => {
if Self::eq25(bytes, b"upgrade-insecure-requests") {
return Some(Self::UpgradeInsecureRequests);
}
None
}
_ => None,
}
}
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Host => "Host",
Self::Accept => "Accept",
Self::AcceptEncoding => "Accept-Encoding",
Self::AcceptLanguage => "Accept-Language",
Self::Authorization => "Authorization",
Self::CacheControl => "Cache-Control",
Self::Connection => "Connection",
Self::ContentType => "Content-Type",
Self::ContentLength => "Content-Length",
Self::Cookie => "Cookie",
Self::IfNoneMatch => "If-None-Match",
Self::IfModifiedSince => "If-Modified-Since",
Self::Origin => "Origin",
Self::Referer => "Referer",
Self::UserAgent => "User-Agent",
Self::TransferEncoding => "Transfer-Encoding",
Self::Upgrade => "Upgrade",
Self::UpgradeInsecureRequests => "Upgrade-Insecure-Requests",
}
}
#[inline]
#[must_use]
pub const fn as_bytes(&self) -> &'static [u8] {
match self {
Self::Host => b"Host",
Self::Accept => b"Accept",
Self::AcceptEncoding => b"Accept-Encoding",
Self::AcceptLanguage => b"Accept-Language",
Self::Authorization => b"Authorization",
Self::CacheControl => b"Cache-Control",
Self::Connection => b"Connection",
Self::ContentType => b"Content-Type",
Self::ContentLength => b"Content-Length",
Self::Cookie => b"Cookie",
Self::IfNoneMatch => b"If-None-Match",
Self::IfModifiedSince => b"If-Modified-Since",
Self::Origin => b"Origin",
Self::Referer => b"Referer",
Self::UserAgent => b"User-Agent",
Self::TransferEncoding => b"Transfer-Encoding",
Self::Upgrade => b"Upgrade",
Self::UpgradeInsecureRequests => b"Upgrade-Insecure-Requests",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResponseHeader {
ContentType,
ContentLength,
CacheControl,
Connection,
Date,
Location,
SetCookie,
Vary,
ETag,
LastModified,
Allow,
Server,
AccessControlAllowOrigin,
}
impl ResponseHeader {
#[allow(dead_code)]
pub(crate) const COUNT: usize = 13;
#[allow(dead_code)]
const _ASSERT_COUNT: () = assert!(Self::AccessControlAllowOrigin as usize + 1 == Self::COUNT);
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ContentType => "Content-Type",
Self::ContentLength => "Content-Length",
Self::CacheControl => "Cache-Control",
Self::Connection => "Connection",
Self::Date => "Date",
Self::Location => "Location",
Self::SetCookie => "Set-Cookie",
Self::Vary => "Vary",
Self::ETag => "ETag",
Self::LastModified => "Last-Modified",
Self::Allow => "Allow",
Self::Server => "Server",
Self::AccessControlAllowOrigin => "Access-Control-Allow-Origin",
}
}
#[inline]
#[must_use]
pub const fn as_bytes(&self) -> &'static [u8] {
match self {
Self::ContentType => b"Content-Type",
Self::ContentLength => b"Content-Length",
Self::CacheControl => b"Cache-Control",
Self::Connection => b"Connection",
Self::Date => b"Date",
Self::Location => b"Location",
Self::SetCookie => b"Set-Cookie",
Self::Vary => b"Vary",
Self::ETag => b"ETag",
Self::LastModified => b"Last-Modified",
Self::Allow => b"Allow",
Self::Server => b"Server",
Self::AccessControlAllowOrigin => b"Access-Control-Allow-Origin",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StatusCode {
Continue,
SwitchingProtocols,
Ok,
Created,
Accepted,
NoContent,
MovedPermanently,
Found,
SeeOther,
NotModified,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
Forbidden,
NotFound,
MethodNotAllowed,
Conflict,
Gone,
LengthRequired,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
UnprocessableEntity,
TooManyRequests,
InternalServerError,
NotImplemented,
BadGateway,
ServiceUnavailable,
GatewayTimeout,
}
impl StatusCode {
#[must_use]
pub const fn code(self) -> u16 {
match self {
Self::Continue => 100,
Self::SwitchingProtocols => 101,
Self::Ok => 200,
Self::Created => 201,
Self::Accepted => 202,
Self::NoContent => 204,
Self::MovedPermanently => 301,
Self::Found => 302,
Self::SeeOther => 303,
Self::NotModified => 304,
Self::TemporaryRedirect => 307,
Self::PermanentRedirect => 308,
Self::BadRequest => 400,
Self::Unauthorized => 401,
Self::Forbidden => 403,
Self::NotFound => 404,
Self::MethodNotAllowed => 405,
Self::Conflict => 409,
Self::Gone => 410,
Self::LengthRequired => 411,
Self::PayloadTooLarge => 413,
Self::UriTooLong => 414,
Self::UnsupportedMediaType => 415,
Self::UnprocessableEntity => 422,
Self::TooManyRequests => 429,
Self::InternalServerError => 500,
Self::NotImplemented => 501,
Self::BadGateway => 502,
Self::ServiceUnavailable => 503,
Self::GatewayTimeout => 504,
}
}
#[must_use]
pub const fn reason(self) -> &'static str {
match self {
Self::Continue => "Continue",
Self::SwitchingProtocols => "Switching Protocols",
Self::Ok => "OK",
Self::Created => "Created",
Self::Accepted => "Accepted",
Self::NoContent => "No Content",
Self::MovedPermanently => "Moved Permanently",
Self::Found => "Found",
Self::SeeOther => "See Other",
Self::NotModified => "Not Modified",
Self::TemporaryRedirect => "Temporary Redirect",
Self::PermanentRedirect => "Permanent Redirect",
Self::BadRequest => "Bad Request",
Self::Unauthorized => "Unauthorized",
Self::Forbidden => "Forbidden",
Self::NotFound => "Not Found",
Self::MethodNotAllowed => "Method Not Allowed",
Self::Conflict => "Conflict",
Self::Gone => "Gone",
Self::LengthRequired => "Length Required",
Self::PayloadTooLarge => "Payload Too Large",
Self::UriTooLong => "URI Too Long",
Self::UnsupportedMediaType => "Unsupported Media Type",
Self::UnprocessableEntity => "Unprocessable Entity",
Self::TooManyRequests => "Too Many Requests",
Self::InternalServerError => "Internal Server Error",
Self::NotImplemented => "Not Implemented",
Self::BadGateway => "Bad Gateway",
Self::ServiceUnavailable => "Service Unavailable",
Self::GatewayTimeout => "Gateway Timeout",
}
}
#[must_use]
pub const fn status_line(self) -> &'static [u8] {
match self {
Self::Continue => b"HTTP/1.1 100 Continue\r\n",
Self::SwitchingProtocols => b"HTTP/1.1 101 Switching Protocols\r\n",
Self::Ok => b"HTTP/1.1 200 OK\r\n",
Self::Created => b"HTTP/1.1 201 Created\r\n",
Self::Accepted => b"HTTP/1.1 202 Accepted\r\n",
Self::NoContent => b"HTTP/1.1 204 No Content\r\n",
Self::MovedPermanently => b"HTTP/1.1 301 Moved Permanently\r\n",
Self::Found => b"HTTP/1.1 302 Found\r\n",
Self::SeeOther => b"HTTP/1.1 303 See Other\r\n",
Self::NotModified => b"HTTP/1.1 304 Not Modified\r\n",
Self::TemporaryRedirect => b"HTTP/1.1 307 Temporary Redirect\r\n",
Self::PermanentRedirect => b"HTTP/1.1 308 Permanent Redirect\r\n",
Self::BadRequest => b"HTTP/1.1 400 Bad Request\r\n",
Self::Unauthorized => b"HTTP/1.1 401 Unauthorized\r\n",
Self::Forbidden => b"HTTP/1.1 403 Forbidden\r\n",
Self::NotFound => b"HTTP/1.1 404 Not Found\r\n",
Self::MethodNotAllowed => b"HTTP/1.1 405 Method Not Allowed\r\n",
Self::Conflict => b"HTTP/1.1 409 Conflict\r\n",
Self::Gone => b"HTTP/1.1 410 Gone\r\n",
Self::LengthRequired => b"HTTP/1.1 411 Length Required\r\n",
Self::PayloadTooLarge => b"HTTP/1.1 413 Payload Too Large\r\n",
Self::UriTooLong => b"HTTP/1.1 414 URI Too Long\r\n",
Self::UnsupportedMediaType => b"HTTP/1.1 415 Unsupported Media Type\r\n",
Self::UnprocessableEntity => b"HTTP/1.1 422 Unprocessable Entity\r\n",
Self::TooManyRequests => b"HTTP/1.1 429 Too Many Requests\r\n",
Self::InternalServerError => b"HTTP/1.1 500 Internal Server Error\r\n",
Self::NotImplemented => b"HTTP/1.1 501 Not Implemented\r\n",
Self::BadGateway => b"HTTP/1.1 502 Bad Gateway\r\n",
Self::ServiceUnavailable => b"HTTP/1.1 503 Service Unavailable\r\n",
Self::GatewayTimeout => b"HTTP/1.1 504 Gateway Timeout\r\n",
}
}
}
impl fmt::Display for StatusCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.code(), self.reason())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn method_display() {
assert_eq!(Method::Get.to_string(), "GET");
assert_eq!(Method::Post.to_string(), "POST");
assert_eq!(Method::Options.to_string(), "OPTIONS");
}
#[test]
fn http_version_display() {
assert_eq!(HttpVersion::Http10.to_string(), "HTTP/1.0");
assert_eq!(HttpVersion::Http11.to_string(), "HTTP/1.1");
}
#[test]
fn status_code_display() {
assert_eq!(StatusCode::Ok.to_string(), "200 OK");
assert_eq!(StatusCode::NotFound.to_string(), "404 Not Found");
assert_eq!(
StatusCode::InternalServerError.to_string(),
"500 Internal Server Error"
);
}
}