#![deny(unsafe_code)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
pub mod normalize;
pub const MAX_PATH_LEN: usize = 1024;
pub const MAX_QUERY_LEN: usize = 1024;
pub const MAX_AUTHORITY_LEN: usize = 256;
pub const MAX_HEADER_COUNT: usize = 32;
pub const MAX_HEADER_NAME_LEN: usize = 64;
pub const MAX_HEADER_VALUE_LEN: usize = 256;
pub const MAX_BODY_LEN: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Protocol {
Http1,
Http2,
Http3,
Tcp,
Udp,
Quic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Transport {
Plaintext,
Tls13,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Method {
Get,
Post,
Put,
Delete,
Patch,
Head,
Options,
Connect,
Trace,
}
impl Method {
#[inline]
pub fn as_str(&self) -> &'static str {
match self {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Patch => "PATCH",
Method::Head => "HEAD",
Method::Options => "OPTIONS",
Method::Connect => "CONNECT",
Method::Trace => "TRACE",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodParseError(());
impl std::fmt::Display for MethodParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("invalid HTTP method")
}
}
impl std::error::Error for MethodParseError {}
impl std::str::FromStr for Method {
type Err = MethodParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"GET" => Method::Get,
"POST" => Method::Post,
"PUT" => Method::Put,
"DELETE" => Method::Delete,
"PATCH" => Method::Patch,
"HEAD" => Method::Head,
"OPTIONS" => Method::Options,
"CONNECT" => Method::Connect,
"TRACE" => Method::Trace,
_ => return Err(MethodParseError(())),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct CanonicalHeader {
pub name: [u8; MAX_HEADER_NAME_LEN],
pub name_len: u8,
pub value: [u8; MAX_HEADER_VALUE_LEN],
pub value_len: u16,
}
impl CanonicalHeader {
#[inline]
pub const fn empty() -> Self {
Self {
name: [0u8; MAX_HEADER_NAME_LEN],
name_len: 0,
value: [0u8; MAX_HEADER_VALUE_LEN],
value_len: 0,
}
}
#[inline]
pub fn reset(&mut self) {
self.name_len = 0;
self.value_len = 0;
}
pub fn from_bytes(name: &[u8], value: &[u8]) -> Option<Self> {
if name.len() > MAX_HEADER_NAME_LEN || value.len() > MAX_HEADER_VALUE_LEN {
return None;
}
let mut hdr = Self::empty();
let name_len = name.len() as u8;
hdr.name[..name.len()].copy_from_slice(name);
hdr.name_len = name_len;
let value_len = value.len() as u16;
hdr.value[..value.len()].copy_from_slice(value);
hdr.value_len = value_len;
Some(hdr)
}
#[inline]
pub fn name_str(&self) -> &str {
core::str::from_utf8(&self.name[..self.name_len as usize]).unwrap_or("")
}
#[inline]
pub fn value_str(&self) -> &str {
core::str::from_utf8(&self.value[..self.value_len as usize]).unwrap_or("")
}
#[inline]
pub fn is_empty(&self) -> bool {
self.name_len == 0
}
}
#[derive(Debug)]
pub struct BodyStream<'a> {
data: &'a [u8],
consumed: bool,
}
impl<'a> BodyStream<'a> {
#[inline]
pub fn new(data: &'a [u8]) -> Self {
Self {
data,
consumed: false,
}
}
#[inline]
pub fn get_data(&mut self) -> Option<&'a [u8]> {
if self.consumed || self.data.is_empty() {
return None;
}
self.consumed = true;
Some(self.data)
}
#[inline]
pub fn is_consumed(&self) -> bool {
self.consumed
}
#[inline]
pub fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct CanonicalRequest {
pub method: Method,
scheme: [u8; 8],
scheme_len: u8,
authority: [u8; MAX_AUTHORITY_LEN],
authority_len: u16,
normalized_path: [u8; MAX_PATH_LEN],
path_len: u16,
normalized_query: [u8; MAX_QUERY_LEN],
query_len: u32,
headers: [CanonicalHeader; MAX_HEADER_COUNT],
header_count: u8,
pub protocol: Protocol,
pub transport: Transport,
pub generation: u64,
pub deadline_ms: u64,
pub cancelled: bool,
pub resource_lease: u64,
body_buffer: [u8; MAX_BODY_LEN],
body_len: u32,
body_overflow: Option<Vec<u8>>,
}
impl CanonicalRequest {
pub const fn empty() -> Self {
Self {
method: Method::Get,
scheme: [0u8; 8],
scheme_len: 0,
authority: [0u8; MAX_AUTHORITY_LEN],
authority_len: 0,
normalized_path: [0u8; MAX_PATH_LEN],
path_len: 0,
normalized_query: [0u8; MAX_QUERY_LEN],
query_len: 0,
headers: [CanonicalHeader::empty(); MAX_HEADER_COUNT],
header_count: 0,
protocol: Protocol::Http1,
transport: Transport::Plaintext,
generation: 0,
deadline_ms: 0,
cancelled: false,
resource_lease: 0,
body_buffer: [0u8; MAX_BODY_LEN],
body_len: 0,
body_overflow: None,
}
}
#[inline]
pub fn reset(&mut self) {
self.method = Method::Get;
self.scheme_len = 0;
self.authority_len = 0;
self.path_len = 0;
self.query_len = 0;
for i in 0..self.header_count as usize {
self.headers[i].reset();
}
self.header_count = 0;
self.protocol = Protocol::Http1;
self.transport = Transport::Plaintext;
self.generation = 0;
self.deadline_ms = 0;
self.cancelled = false;
self.resource_lease = 0;
self.body_len = 0;
self.body_overflow = None;
}
pub fn set_body(&mut self, body: impl Into<Vec<u8>>) {
let body = body.into();
if body.len() <= MAX_BODY_LEN {
self.body_overflow = None;
self.body_buffer[..body.len()].copy_from_slice(&body);
self.body_len = body.len() as u32;
} else {
self.body_overflow = Some(body);
self.body_len = self
.body_overflow
.as_ref()
.map(|v| v.len())
.unwrap_or(0) as u32;
}
}
#[inline]
pub fn body(&self) -> &[u8] {
if let Some(ref overflow) = self.body_overflow {
overflow.as_slice()
} else {
&self.body_buffer[..self.body_len as usize]
}
}
#[inline]
pub fn scheme_str(&self) -> &str {
core::str::from_utf8(&self.scheme[..self.scheme_len as usize]).unwrap_or("")
}
#[must_use]
pub fn set_scheme(&mut self, scheme: &str) -> bool {
if scheme.len() > 8 {
return false;
}
let len = scheme.len() as u8;
self.scheme[..scheme.len()].copy_from_slice(scheme.as_bytes());
self.scheme_len = len;
true
}
#[inline]
pub fn authority_str(&self) -> &str {
core::str::from_utf8(&self.authority[..self.authority_len as usize]).unwrap_or("")
}
#[must_use]
pub fn set_authority(&mut self, authority: &str) -> bool {
if authority.len() > MAX_AUTHORITY_LEN {
return false;
}
let len = authority.len() as u16;
self.authority[..authority.len()].copy_from_slice(authority.as_bytes());
self.authority_len = len;
true
}
#[inline]
pub fn path_str(&self) -> &str {
core::str::from_utf8(&self.normalized_path[..self.path_len as usize]).unwrap_or("")
}
#[inline]
pub fn path_bytes(&self) -> &[u8] {
&self.normalized_path[..self.path_len as usize]
}
#[must_use]
pub fn set_path(&mut self, path: &str) -> bool {
let (path_part, query_part) = match path.find('?') {
Some(pos) => (&path[..pos], Some(&path[pos + 1..])),
None => (path, None),
};
if path_part.len() > MAX_PATH_LEN {
return false;
}
if let Some(query) = query_part
&& query.len() > MAX_QUERY_LEN
{
return false;
}
self.normalized_path[..path_part.len()].copy_from_slice(path_part.as_bytes());
self.path_len = path_part.len() as u16;
if let Some(query) = query_part {
let _ = self.set_query(query);
}
true
}
#[inline]
pub fn query_str(&self) -> &str {
core::str::from_utf8(&self.normalized_query[..self.query_len as usize]).unwrap_or("")
}
#[must_use]
pub fn set_query(&mut self, query: &str) -> bool {
if query.len() > MAX_QUERY_LEN {
return false;
}
let len = query.len() as u32;
self.normalized_query[..query.len()].copy_from_slice(query.as_bytes());
self.query_len = len;
true
}
#[inline]
pub fn header_count(&self) -> u8 {
self.header_count
}
#[inline]
pub fn get_header(&self, idx: usize) -> Option<&CanonicalHeader> {
if idx < self.header_count as usize {
Some(&self.headers[idx])
} else {
None
}
}
#[inline]
pub fn find_header(&self, name: &str) -> Option<&CanonicalHeader> {
let name_bytes = name.as_bytes();
for i in 0..self.header_count as usize {
let hdr = &self.headers[i];
if hdr.name_len as usize == name_bytes.len()
&& hdr.name[..hdr.name_len as usize].eq_ignore_ascii_case(name_bytes)
{
return Some(hdr);
}
}
None
}
pub fn add_header(&mut self, name: &[u8], value: &[u8]) -> Result<(), &'static str> {
if self.header_count as usize >= MAX_HEADER_COUNT {
return Err("header count exceeded");
}
if name.len() > MAX_HEADER_NAME_LEN {
return Err("header name too long");
}
if value.len() > MAX_HEADER_VALUE_LEN {
return Err("header value too long");
}
if name.contains(&b'\r') || name.contains(&b'\n') {
return Err("header name contains CRLF");
}
if value.contains(&b'\r') || value.contains(&b'\n') {
return Err("header value contains CRLF");
}
let idx = self.header_count as usize;
self.headers[idx] = match CanonicalHeader::from_bytes(name, value) {
Some(hdr) => hdr,
None => return Err("header name or value too long"),
};
self.header_count += 1;
Ok(())
}
#[inline]
pub fn headers_iter(&self) -> &[CanonicalHeader] {
&self.headers[..self.header_count as usize]
}
#[inline]
pub fn cancel(&mut self) {
self.cancelled = true;
}
#[inline]
pub fn is_cancelled(&self) -> bool {
self.cancelled
}
}
#[derive(Debug, Clone)]
pub struct CanonicalResponse {
pub status_code: u16,
headers: [CanonicalHeader; MAX_HEADER_COUNT],
header_count: u8,
body_buffer: [u8; MAX_BODY_LEN],
body_len: u32,
body_overflow: Option<Vec<u8>>,
}
impl CanonicalResponse {
#[inline]
pub const fn new(status_code: u16) -> Self {
Self {
status_code,
headers: [CanonicalHeader::empty(); MAX_HEADER_COUNT],
header_count: 0,
body_buffer: [0u8; MAX_BODY_LEN],
body_len: 0,
body_overflow: None,
}
}
#[inline]
pub const fn ok() -> Self {
Self::new(200)
}
#[inline]
pub const fn error(status_code: u16) -> Self {
Self::new(status_code)
}
#[inline]
pub fn reset(&mut self) {
for i in 0..self.header_count as usize {
self.headers[i].reset();
}
self.header_count = 0;
self.body_len = 0;
self.body_overflow = None;
}
#[inline]
pub fn header_count(&self) -> u8 {
self.header_count
}
pub fn add_header(&mut self, name: &[u8], value: &[u8]) -> Result<(), &'static str> {
if self.header_count as usize >= MAX_HEADER_COUNT {
return Err("header count exceeded");
}
if name.contains(&b'\r') || name.contains(&b'\n') {
return Err("header name contains CRLF");
}
if value.contains(&b'\r') || value.contains(&b'\n') {
return Err("header value contains CRLF");
}
let idx = self.header_count as usize;
self.headers[idx] = CanonicalHeader::from_bytes(name, value)
.ok_or("header name/value too long")?;
self.header_count += 1;
Ok(())
}
pub fn set_body(&mut self, body: impl Into<Vec<u8>>) {
let body = body.into();
if body.len() <= MAX_BODY_LEN {
self.body_overflow = None;
self.body_buffer[..body.len()].copy_from_slice(&body);
self.body_len = body.len() as u32;
} else {
self.body_overflow = Some(body);
self.body_len = self.body_overflow.as_ref().map(|v| v.len()).unwrap_or(0) as u32;
}
}
#[inline]
pub fn body(&self) -> &[u8] {
if let Some(ref overflow) = self.body_overflow {
overflow
} else {
&self.body_buffer[..self.body_len as usize]
}
}
#[inline]
pub fn headers_iter(&self) -> &[CanonicalHeader] {
&self.headers[..self.header_count as usize]
}
#[inline]
pub fn find_header(&self, name: &str) -> Option<&CanonicalHeader> {
let name_bytes = name.as_bytes();
for i in 0..self.header_count as usize {
let hdr = &self.headers[i];
if hdr.name_len as usize == name_bytes.len()
&& hdr.name[..hdr.name_len as usize].eq_ignore_ascii_case(name_bytes)
{
return Some(hdr);
}
}
None
}
}
impl Default for CanonicalRequest {
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl Default for CanonicalResponse {
#[inline]
fn default() -> Self {
Self::new(200)
}
}
pub trait Service: Send + Sync + 'static {
fn handle(&self, request: CanonicalRequest) -> CanonicalResponse;
}
pub trait ServiceFactory: Send + Sync + 'static {
fn create(&self) -> Box<dyn Service>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_method_from_str() {
use std::str::FromStr;
assert_eq!(Method::from_str("GET"), Ok(Method::Get));
assert_eq!(Method::from_str("POST"), Ok(Method::Post));
assert!(Method::from_str("post").is_err());
assert!(Method::from_str("Get").is_err());
assert!(Method::from_str("INVALID").is_err());
}
#[test]
fn test_method_as_str() {
assert_eq!(Method::Get.as_str(), "GET");
assert_eq!(Method::Post.as_str(), "POST");
}
#[test]
fn test_canonical_header_creation() {
let hdr = CanonicalHeader::from_bytes(b"content-type", b"application/json").unwrap();
assert_eq!(hdr.name_str(), "content-type");
assert_eq!(hdr.value_str(), "application/json");
assert_eq!(hdr.name_len, 12);
assert_eq!(hdr.value_len, 16);
}
#[test]
fn test_canonical_header_overlong_rejected() {
let long_name = b"this-is-a-very-long-header-name-that-should-exceed-the-maximum-sixty-four-bytes-limit-and-test-truncation";
assert!(long_name.len() > MAX_HEADER_NAME_LEN);
assert!(CanonicalHeader::from_bytes(long_name, b"value").is_none());
let exact_name = vec![b'n'; MAX_HEADER_NAME_LEN];
let hdr = CanonicalHeader::from_bytes(&exact_name, b"value").unwrap();
assert_eq!(hdr.name_len, MAX_HEADER_NAME_LEN as u8);
}
#[test]
fn test_canonical_request_creation() {
let mut request = CanonicalRequest::empty();
request.method = Method::Get;
assert!(request.set_scheme("https"));
assert!(request.set_authority("example.com"));
assert!(request.set_path("/api/test"));
assert!(request.set_query("key=value"));
request.protocol = Protocol::Http1;
request.transport = Transport::Tls13;
assert_eq!(request.method, Method::Get);
assert_eq!(request.scheme_str(), "https");
assert_eq!(request.authority_str(), "example.com");
assert_eq!(request.path_str(), "/api/test");
assert_eq!(request.query_str(), "key=value");
}
#[test]
fn test_canonical_request_headers() {
let mut request = CanonicalRequest::empty();
request
.add_header(b"content-type", b"application/json")
.unwrap();
request
.add_header(b"accept", b"text/html")
.unwrap();
assert_eq!(request.header_count(), 2);
assert!(request.find_header("content-type").is_some());
assert!(request.find_header("non-existent").is_none());
let hdr = request.find_header("accept").unwrap();
assert_eq!(hdr.value_str(), "text/html");
}
#[test]
fn test_canonical_request_header_limit() {
let mut request = CanonicalRequest::empty();
for i in 0..MAX_HEADER_COUNT {
let name = format!("header-{}", i);
request.add_header(name.as_bytes(), b"value").unwrap();
}
assert_eq!(request.header_count(), MAX_HEADER_COUNT as u8);
let result = request.add_header(b"overflow", b"value");
assert!(result.is_err());
}
#[test]
fn test_body_stream() {
let data = b"test body data";
let mut stream = BodyStream::new(data);
assert!(!stream.is_consumed());
assert_eq!(stream.len(), 14);
assert!(!stream.is_empty());
let result = stream.get_data();
assert_eq!(result, Some(&data[..]));
assert!(stream.is_consumed());
let result2 = stream.get_data();
assert!(result2.is_none());
}
#[test]
fn test_body_stream_empty() {
let mut stream = BodyStream::new(b"");
assert!(stream.is_empty());
let result = stream.get_data();
assert!(result.is_none());
}
#[test]
fn test_governance_fields() {
let mut request = CanonicalRequest::empty();
request.generation = 42;
request.deadline_ms = 5000;
request.resource_lease = 100;
assert_eq!(request.generation, 42);
assert_eq!(request.deadline_ms, 5000);
request.cancel();
assert!(request.is_cancelled());
}
#[test]
fn test_canonical_response() {
let mut response = CanonicalResponse::new(200);
assert_eq!(response.status_code, 200);
response
.add_header(b"content-type", b"text/plain")
.unwrap();
assert_eq!(response.header_count(), 1);
response.set_body(b"Hello".to_vec());
assert_eq!(response.body(), b"Hello");
}
#[test]
fn test_canonical_response_helpers() {
let ok = CanonicalResponse::ok();
assert_eq!(ok.status_code, 200);
let error = CanonicalResponse::error(404);
assert_eq!(error.status_code, 404);
}
#[test]
fn test_request_copy_semantics() {
let mut request = CanonicalRequest::empty();
request.method = Method::Post;
assert!(request.set_path("/test"));
request.generation = 1;
let cloned = request.clone();
assert_eq!(cloned.method, Method::Post);
assert_eq!(cloned.path_str(), "/test");
assert_eq!(cloned.generation, 1);
request.method = Method::Get;
assert_ne!(request.method, cloned.method);
}
#[test]
fn test_method_all_variants_as_str() {
assert_eq!(Method::Get.as_str(), "GET");
assert_eq!(Method::Post.as_str(), "POST");
assert_eq!(Method::Put.as_str(), "PUT");
assert_eq!(Method::Delete.as_str(), "DELETE");
assert_eq!(Method::Patch.as_str(), "PATCH");
assert_eq!(Method::Head.as_str(), "HEAD");
assert_eq!(Method::Options.as_str(), "OPTIONS");
assert_eq!(Method::Connect.as_str(), "CONNECT");
assert_eq!(Method::Trace.as_str(), "TRACE");
}
#[test]
fn test_method_from_str_all_variants() {
use std::str::FromStr;
assert_eq!(Method::from_str("GET"), Ok(Method::Get));
assert_eq!(Method::from_str("POST"), Ok(Method::Post));
assert_eq!(Method::from_str("PUT"), Ok(Method::Put));
assert_eq!(Method::from_str("DELETE"), Ok(Method::Delete));
assert_eq!(Method::from_str("PATCH"), Ok(Method::Patch));
assert_eq!(Method::from_str("HEAD"), Ok(Method::Head));
assert_eq!(Method::from_str("OPTIONS"), Ok(Method::Options));
assert_eq!(Method::from_str("CONNECT"), Ok(Method::Connect));
assert_eq!(Method::from_str("TRACE"), Ok(Method::Trace));
}
#[test]
fn test_method_from_str_case_sensitive_strict() {
use std::str::FromStr;
assert!(Method::from_str("get").is_err());
assert!(Method::from_str("Get").is_err());
assert!(Method::from_str("gEt").is_err());
assert!(Method::from_str("post").is_err());
assert!(Method::from_str("Post").is_err());
assert!(Method::from_str("delete").is_err());
assert!(Method::from_str(" GET").is_err());
assert!(Method::from_str("GET ").is_err());
}
#[test]
fn test_method_from_str_invalid() {
use std::str::FromStr;
assert!(Method::from_str("").is_err());
assert!(Method::from_str("UNKNOWN").is_err());
assert!(Method::from_str("GET ").is_err());
assert!(Method::from_str(" GET").is_err());
assert!(Method::from_str("123").is_err());
}
#[test]
fn test_method_parse_error_display() {
use std::str::FromStr;
let err = Method::from_str("INVALID").unwrap_err();
assert_eq!(format!("{}", err), "invalid HTTP method");
assert!(format!("{:?}", err).contains("MethodParseError"));
}
#[test]
fn test_protocol_variants() {
let protocols = [
Protocol::Http1,
Protocol::Http2,
Protocol::Http3,
Protocol::Tcp,
Protocol::Udp,
Protocol::Quic,
];
for (i, p) in protocols.iter().enumerate() {
for (j, q) in protocols.iter().enumerate() {
if i == j {
assert_eq!(p, q);
} else {
assert_ne!(p, q);
}
}
}
}
#[test]
fn test_transport_variants() {
assert_eq!(Transport::Plaintext, Transport::Plaintext);
assert_eq!(Transport::Tls13, Transport::Tls13);
assert_ne!(Transport::Plaintext, Transport::Tls13);
}
#[test]
fn test_canonical_header_empty() {
let hdr = CanonicalHeader::empty();
assert!(hdr.is_empty());
assert_eq!(hdr.name_len, 0);
assert_eq!(hdr.value_len, 0);
assert_eq!(hdr.name_str(), "");
assert_eq!(hdr.value_str(), "");
}
#[test]
fn test_canonical_header_value_overlong_rejected() {
let long_value = vec![b'x'; MAX_HEADER_VALUE_LEN + 100];
assert!(CanonicalHeader::from_bytes(b"name", &long_value).is_none());
let exact_value = vec![b'x'; MAX_HEADER_VALUE_LEN];
let hdr = CanonicalHeader::from_bytes(b"name", &exact_value).unwrap();
assert_eq!(hdr.value_len, MAX_HEADER_VALUE_LEN as u16);
assert_eq!(hdr.value_str().len(), MAX_HEADER_VALUE_LEN);
}
#[test]
fn test_canonical_header_zero_length() {
let hdr = CanonicalHeader::from_bytes(b"", b"").unwrap();
assert!(hdr.is_empty());
assert_eq!(hdr.name_str(), "");
assert_eq!(hdr.value_str(), "");
}
#[test]
fn test_canonical_header_invalid_utf8() {
let invalid_utf8 = [0xFF, 0xFE, 0xFD];
let hdr = CanonicalHeader::from_bytes(&invalid_utf8, b"value").unwrap();
assert_eq!(hdr.name_str(), "");
}
#[test]
fn test_canonical_request_full_fields() {
let mut req = CanonicalRequest::empty();
req.method = Method::Put;
assert!(req.set_scheme("https"));
assert!(req.set_authority("api.example.com:8443"));
assert!(req.set_path("/api/v1/users/42"));
assert!(req.set_query("id=42&name=test&verbose=true"));
req.protocol = Protocol::Http2;
req.transport = Transport::Tls13;
req.generation = 12345;
req.deadline_ms = 30000;
req.cancelled = false;
req.resource_lease = 999;
assert_eq!(req.method, Method::Put);
assert_eq!(req.scheme_str(), "https");
assert_eq!(req.authority_str(), "api.example.com:8443");
assert_eq!(req.path_str(), "/api/v1/users/42");
assert_eq!(req.query_str(), "id=42&name=test&verbose=true");
assert_eq!(req.protocol, Protocol::Http2);
assert_eq!(req.transport, Transport::Tls13);
assert_eq!(req.generation, 12345);
assert_eq!(req.deadline_ms, 30000);
assert!(!req.is_cancelled());
assert_eq!(req.resource_lease, 999);
}
#[test]
fn test_canonical_request_scheme_overlong_rejected() {
let mut req = CanonicalRequest::empty();
assert!(req.set_scheme("https"));
assert!(!req.set_scheme("https-and-more"));
assert_eq!(req.scheme_str(), "https");
let mut req2 = CanonicalRequest::empty();
assert!(req2.set_scheme("12345678"));
assert_eq!(req2.scheme_str(), "12345678");
}
#[test]
fn test_canonical_request_path_overlong_rejected() {
let mut req = CanonicalRequest::empty();
assert!(req.set_path("/original"));
let long_path = "a".repeat(MAX_PATH_LEN + 100);
assert!(!req.set_path(&long_path));
assert_eq!(req.path_str(), "/original");
let exact_path = "a".repeat(MAX_PATH_LEN);
assert!(req.set_path(&exact_path));
assert_eq!(req.path_str().len(), MAX_PATH_LEN);
}
#[test]
fn test_set_path_splits_query() {
let mut req = CanonicalRequest::empty();
assert!(req.set_path("/api/users?id=42&verbose=true"));
assert_eq!(req.path_str(), "/api/users");
assert_eq!(req.query_str(), "id=42&verbose=true");
let mut req2 = CanonicalRequest::empty();
assert!(req2.set_query("keep=1"));
assert!(req2.set_path("/plain/path"));
assert_eq!(req2.path_str(), "/plain/path");
assert_eq!(req2.query_str(), "keep=1");
let mut req3 = CanonicalRequest::empty();
assert!(req3.set_path("/api?"));
assert_eq!(req3.path_str(), "/api");
assert_eq!(req3.query_str(), "");
}
#[test]
fn test_set_path_overlong_split_query_atomic_rejected() {
let mut req = CanonicalRequest::empty();
assert!(req.set_query("keep=1"));
let long_target = format!("/ok?{}", "a".repeat(MAX_QUERY_LEN + 1));
assert!(!req.set_path(&long_target));
assert_eq!(req.path_str(), "");
assert_eq!(req.query_str(), "keep=1");
}
#[test]
fn test_add_header_overlong_rejected() {
let mut req = CanonicalRequest::empty();
let long_name = vec![b'n'; MAX_HEADER_NAME_LEN + 1];
let result = req.add_header(&long_name, b"value");
assert_eq!(result, Err("header name too long"));
let long_value = vec![b'v'; MAX_HEADER_VALUE_LEN + 1];
let result = req.add_header(b"name", &long_value);
assert_eq!(result, Err("header value too long"));
assert_eq!(req.header_count(), 0);
let exact_name = vec![b'n'; MAX_HEADER_NAME_LEN];
let exact_value = vec![b'v'; MAX_HEADER_VALUE_LEN];
assert!(req.add_header(&exact_name, &exact_value).is_ok());
assert_eq!(req.header_count(), 1);
}
#[test]
fn test_response_add_header_overlong_rejected() {
let mut resp = CanonicalResponse::new(200);
let long_name = vec![b'n'; MAX_HEADER_NAME_LEN + 1];
assert!(resp.add_header(&long_name, b"value").is_err());
let long_value = vec![b'v'; MAX_HEADER_VALUE_LEN + 1];
assert!(resp.add_header(b"name", &long_value).is_err());
assert_eq!(resp.header_count(), 0);
}
#[test]
fn test_canonical_request_query_overlong_rejected() {
let mut req = CanonicalRequest::empty();
assert!(req.set_query("keep=1"));
let long_query = "a".repeat(MAX_QUERY_LEN + 100);
assert!(!req.set_query(&long_query));
assert_eq!(req.query_str(), "keep=1");
let exact_query = "a".repeat(MAX_QUERY_LEN);
assert!(req.set_query(&exact_query));
assert_eq!(req.query_str().len(), MAX_QUERY_LEN);
}
#[test]
fn test_canonical_request_authority_overlong_rejected() {
let mut req = CanonicalRequest::empty();
assert!(req.set_authority("example.com"));
let long_auth = "a".repeat(MAX_AUTHORITY_LEN + 100);
assert!(!req.set_authority(&long_auth));
assert_eq!(req.authority_str(), "example.com");
let exact_auth = "a".repeat(MAX_AUTHORITY_LEN);
assert!(req.set_authority(&exact_auth));
assert_eq!(req.authority_str().len(), MAX_AUTHORITY_LEN);
}
#[test]
fn test_canonical_request_empty_strings() {
let mut req = CanonicalRequest::empty();
assert!(req.set_scheme(""));
assert!(req.set_authority(""));
assert!(req.set_path(""));
assert!(req.set_query(""));
assert_eq!(req.scheme_str(), "");
assert_eq!(req.authority_str(), "");
assert_eq!(req.path_str(), "");
assert_eq!(req.query_str(), "");
}
#[test]
fn test_canonical_request_get_header_boundary() {
let mut req = CanonicalRequest::empty();
req.add_header(b"h1", b"v1").unwrap();
req.add_header(b"h2", b"v2").unwrap();
assert!(req.get_header(0).is_some());
assert!(req.get_header(1).is_some());
assert!(req.get_header(2).is_none());
assert!(req.get_header(100).is_none());
}
#[test]
fn test_canonical_request_find_header_case_insensitive() {
let mut req = CanonicalRequest::empty();
req.add_header(b"Content-Type", b"application/json").unwrap();
assert!(req.find_header("Content-Type").is_some());
assert!(req.find_header("content-type").is_some());
assert!(req.find_header("CONTENT-TYPE").is_some());
assert!(req.find_header("cOnTeNt-tYpE").is_some());
assert!(req.find_header("content-length").is_none());
}
#[test]
fn test_canonical_request_headers_iter() {
let mut req = CanonicalRequest::empty();
req.add_header(b"a", b"1").unwrap();
req.add_header(b"b", b"2").unwrap();
req.add_header(b"c", b"3").unwrap();
let headers = req.headers_iter();
assert_eq!(headers.len(), 3);
assert_eq!(headers[0].name_str(), "a");
assert_eq!(headers[1].name_str(), "b");
assert_eq!(headers[2].name_str(), "c");
}
#[test]
fn test_canonical_request_headers_iter_empty() {
let req = CanonicalRequest::empty();
assert!(req.headers_iter().is_empty());
}
#[test]
fn test_canonical_request_cancel() {
let mut req = CanonicalRequest::empty();
assert!(!req.is_cancelled());
req.cancel();
assert!(req.is_cancelled());
req.cancel();
assert!(req.is_cancelled());
}
#[test]
fn test_canonical_request_deadline_zero() {
let mut req = CanonicalRequest::empty();
assert_eq!(req.deadline_ms, 0);
req.deadline_ms = 0;
assert_eq!(req.deadline_ms, 0);
}
#[test]
fn test_canonical_request_resource_lease() {
let mut req = CanonicalRequest::empty();
assert_eq!(req.resource_lease, 0);
req.resource_lease = u64::MAX;
assert_eq!(req.resource_lease, u64::MAX);
}
#[test]
fn test_body_stream_single_use() {
let data = b"hello";
let mut stream = BodyStream::new(data);
assert_eq!(stream.get_data(), Some(&data[..]));
assert!(stream.is_consumed());
assert!(stream.get_data().is_none());
assert!(stream.get_data().is_none());
}
#[test]
fn test_body_stream_len_unchanged_after_consume() {
let data = b"test data";
let mut stream = BodyStream::new(data);
assert_eq!(stream.len(), 9);
let _ = stream.get_data();
assert_eq!(stream.len(), 9);
}
#[test]
fn test_body_stream_empty_data_never_consumable() {
let mut stream = BodyStream::new(b"");
assert!(stream.is_empty());
assert!(!stream.is_consumed());
assert!(stream.get_data().is_none());
assert!(!stream.is_consumed());
}
#[test]
fn test_body_stream_large_data() {
let large_data = vec![b'x'; 10000];
let mut stream = BodyStream::new(&large_data);
assert_eq!(stream.len(), 10000);
assert!(!stream.is_empty());
let result = stream.get_data().unwrap();
assert_eq!(result.len(), 10000);
}
#[test]
fn test_canonical_response_full_fields() {
let mut resp = CanonicalResponse::new(418);
resp.add_header(b"content-type", b"text/plain").unwrap();
resp.add_header(b"x-custom", b"value").unwrap();
resp.set_body(b"I'm a teapot".to_vec());
assert_eq!(resp.status_code, 418);
assert_eq!(resp.header_count(), 2);
assert_eq!(resp.body(), b"I'm a teapot");
}
#[test]
fn test_canonical_response_header_limit() {
let mut resp = CanonicalResponse::new(200);
for i in 0..MAX_HEADER_COUNT {
let name = format!("h{}", i);
resp.add_header(name.as_bytes(), b"v").unwrap();
}
assert_eq!(resp.header_count(), MAX_HEADER_COUNT as u8);
assert!(resp.add_header(b"overflow", b"v").is_err());
}
#[test]
fn test_canonical_response_find_header() {
let mut resp = CanonicalResponse::new(200);
resp.add_header(b"Server", b"zenith").unwrap();
resp.add_header(b"Date", b"today").unwrap();
assert!(resp.find_header("Server").is_some());
assert!(resp.find_header("Date").is_some());
assert!(resp.find_header("server").is_some());
assert!(resp.find_header("DATE").is_some());
assert!(resp.find_header("X-Missing").is_none());
}
#[test]
fn test_canonical_response_headers_iter() {
let mut resp = CanonicalResponse::new(200);
resp.add_header(b"a", b"1").unwrap();
resp.add_header(b"b", b"2").unwrap();
let headers = resp.headers_iter();
assert_eq!(headers.len(), 2);
}
#[test]
fn test_canonical_response_body_empty() {
let resp = CanonicalResponse::new(204);
assert!(resp.body().is_empty());
}
#[test]
fn test_canonical_response_body_replace() {
let mut resp = CanonicalResponse::new(200);
resp.set_body(b"first".to_vec());
assert_eq!(resp.body(), b"first");
resp.set_body(b"second".to_vec());
assert_eq!(resp.body(), b"second");
}
struct EchoService;
impl Service for EchoService {
fn handle(&self, request: CanonicalRequest) -> CanonicalResponse {
let mut resp = CanonicalResponse::ok();
resp.set_body(request.path_str().as_bytes().to_vec());
resp
}
}
struct EchoServiceFactory;
impl ServiceFactory for EchoServiceFactory {
fn create(&self) -> Box<dyn Service> {
Box::new(EchoService)
}
}
#[test]
fn test_service_trait_object() {
let service: Box<dyn Service> = Box::new(EchoService);
let mut req = CanonicalRequest::empty();
assert!(req.set_path("/hello"));
let resp = service.handle(req);
assert_eq!(resp.status_code, 200);
assert_eq!(resp.body(), b"/hello");
}
#[test]
fn test_service_factory_trait() {
let factory = EchoServiceFactory;
let service = factory.create();
let mut req = CanonicalRequest::empty();
assert!(req.set_path("/test"));
let resp = service.handle(req);
assert_eq!(resp.body(), b"/test");
}
#[test]
fn test_add_header_rejects_crlf() {
let mut req = CanonicalRequest::empty();
assert_eq!(
req.add_header(b"bad\rname", b"v"),
Err("header name contains CRLF")
);
assert_eq!(
req.add_header(b"bad\nname", b"v"),
Err("header name contains CRLF")
);
assert_eq!(
req.add_header(b"bad\r\nname", b"v"),
Err("header name contains CRLF")
);
assert_eq!(
req.add_header(b"name", b"bad\rvalue"),
Err("header value contains CRLF")
);
assert_eq!(
req.add_header(b"name", b"bad\nvalue"),
Err("header value contains CRLF")
);
assert!(req.add_header(b"name", b"ok\r\nEvil: x").is_err());
assert_eq!(req.header_count(), 0);
let mut resp = CanonicalResponse::new(200);
assert_eq!(
resp.add_header(b"bad\rname", b"v"),
Err("header name contains CRLF")
);
assert_eq!(
resp.add_header(b"name", b"bad\nvalue"),
Err("header value contains CRLF")
);
assert_eq!(resp.header_count(), 0);
}
#[test]
fn test_request_set_body_overflow_heap_path() {
let mut req = CanonicalRequest::empty();
let big = vec![b'x'; 5000];
assert!(big.len() > MAX_BODY_LEN);
req.set_body(big.clone());
assert!(req.body_overflow.is_some());
assert_eq!(req.body().len(), 5000);
assert_eq!(req.body(), &big[..]);
req.set_body(b"small".to_vec());
assert!(req.body_overflow.is_none());
assert_eq!(req.body(), b"small");
}
}