#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum HttpChar {
Null = 0x00,
HorizontalTab = b'\t',
LineFeed = b'\n',
CarriageReturn = b'\r',
Space = b' ',
Zero = b'0',
Colon = b':',
Delete = 0x7F,
}
impl HttpChar {
#[inline]
#[must_use]
pub const fn as_u8(self) -> u8 {
self as u8
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub const fn as_i8(self) -> i8 {
self as u8 as i8
}
}
impl From<HttpChar> for u8 {
#[inline]
fn from(b: HttpChar) -> Self {
b as Self
}
}
impl From<HttpChar> for i8 {
#[inline]
#[allow(clippy::cast_possible_wrap)]
fn from(b: HttpChar) -> Self {
b as u8 as Self
}
}
impl std::ops::Add<u8> for HttpChar {
type Output = u8;
#[inline]
fn add(self, rhs: u8) -> u8 {
self as u8 + rhs
}
}
pub(crate) const MAX_CONTENT_LENGTH: u64 = 1 << 50;
#[inline]
pub(crate) fn parse_content_length(value: &[u8]) -> Option<u64> {
if value.is_empty() {
return None;
}
if value.len() > 1 && value[0] == b'0' {
return None;
}
let mut n: u64 = 0;
for &b in value {
let d = b.wrapping_sub(b'0');
if d > 9 {
return None;
}
n = n.checked_mul(10)?.checked_add(u64::from(d))?;
}
if n > MAX_CONTENT_LENGTH {
return None;
}
Some(n)
}
impl PartialEq<u8> for HttpChar {
#[inline]
fn eq(&self, other: &u8) -> bool {
*self as u8 == *other
}
}
impl PartialEq<HttpChar> for u8 {
#[inline]
fn eq(&self, other: &HttpChar) -> bool {
*self == *other as Self
}
}
#[cfg(test)]
#[allow(clippy::cast_possible_wrap)]
mod tests {
use super::*;
#[test]
fn repr_values() {
assert_eq!(HttpChar::Null as u8, 0x00);
assert_eq!(HttpChar::HorizontalTab as u8, 0x09);
assert_eq!(HttpChar::LineFeed as u8, 0x0A);
assert_eq!(HttpChar::CarriageReturn as u8, 0x0D);
assert_eq!(HttpChar::Space as u8, 0x20);
assert_eq!(HttpChar::Zero as u8, 0x30);
assert_eq!(HttpChar::Colon as u8, 0x3A);
assert_eq!(HttpChar::Delete as u8, 0x7F);
}
#[test]
fn partial_eq_u8_both_directions() {
let cr: u8 = b'\r';
assert!(cr == HttpChar::CarriageReturn);
assert!(HttpChar::CarriageReturn == cr);
assert!(cr != HttpChar::LineFeed);
assert!(HttpChar::LineFeed != cr);
}
#[test]
fn as_i8_for_simd() {
assert_eq!(HttpChar::CarriageReturn.as_i8(), b'\r' as i8);
assert_eq!(HttpChar::Delete.as_i8(), 0x7F_u8 as i8);
}
#[test]
fn from_conversions() {
let u: u8 = HttpChar::Space.into();
assert_eq!(u, b' ');
let i: i8 = HttpChar::Space.into();
assert_eq!(i, b' ' as i8);
}
#[test]
fn add_u8_for_digit_encoding() {
assert_eq!(HttpChar::Zero + 0, b'0');
assert_eq!(HttpChar::Zero + 5, b'5');
assert_eq!(HttpChar::Zero + 9, b'9');
}
#[test]
fn size_of_is_one() {
assert_eq!(std::mem::size_of::<HttpChar>(), 1);
}
#[test]
fn content_length_simple_values() {
assert_eq!(parse_content_length(b"0"), Some(0));
assert_eq!(parse_content_length(b"5"), Some(5));
assert_eq!(parse_content_length(b"1024"), Some(1024));
}
#[test]
fn content_length_rejects_leading_zero() {
assert_eq!(parse_content_length(b"007"), None);
assert_eq!(parse_content_length(b"01"), None);
}
#[test]
fn content_length_rejects_above_cap() {
let two_pib = (1u64 << 51).to_string();
assert_eq!(parse_content_length(two_pib.as_bytes()), None);
assert_eq!(parse_content_length(b"99999999999999999999"), None);
}
#[test]
fn content_length_at_cap_succeeds() {
let max = MAX_CONTENT_LENGTH.to_string();
assert_eq!(
parse_content_length(max.as_bytes()),
Some(MAX_CONTENT_LENGTH)
);
}
}