use crate::error::ParseError;
use crate::headers::Headers;
use alloc::string::String;
use alloc::vec::Vec;
use bytes::BytesMut;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeaderRef<'a> {
pub name: &'a [u8],
pub value: &'a [u8],
}
pub fn scan_header_fields(input: &[u8]) -> Result<(Vec<HeaderRef<'_>>, &[u8]), ParseError> {
let mut headers = Vec::with_capacity(8);
let remaining = for_each_header_field(input, |h| {
headers.push(h);
Ok(())
})?;
Ok((headers, remaining))
}
#[must_use]
pub fn materialize_headers(refs: &[HeaderRef<'_>]) -> Headers {
let mut byte_cap = 0usize;
for h in refs {
byte_cap = byte_cap
.saturating_add(h.name.len())
.saturating_add(h.value.len());
}
let mut buf = BytesMut::with_capacity(byte_cap);
let mut spans = Vec::with_capacity(refs.len());
for &h in refs {
push_span(&mut buf, &mut spans, h);
}
Headers::from_spans(buf.freeze(), spans)
}
#[must_use]
pub(crate) fn try_wire_spans(
section: &[u8],
refs: &[HeaderRef<'_>],
) -> Option<Vec<(u32, u32, u32, u32)>> {
if refs.iter().any(|h| !h.value.is_ascii()) {
return None;
}
let base = section.as_ptr() as usize;
let section_len = section.len();
let mut spans = Vec::with_capacity(refs.len());
for h in refs {
let name_start = subslice_offset(base, section_len, h.name)?;
let value_start = subslice_offset(base, section_len, h.value)?;
spans.push((
usize_as_u32(name_start),
usize_as_u32(h.name.len()),
usize_as_u32(value_start),
usize_as_u32(h.value.len()),
));
}
Some(spans)
}
#[inline]
fn subslice_offset(
base: usize,
parent_len: usize,
slice: &[u8],
) -> Option<usize> {
let start = slice.as_ptr() as usize;
if start < base {
return None;
}
let offset = start.saturating_sub(base);
if offset.saturating_add(slice.len()) > parent_len {
return None;
}
Some(offset)
}
pub fn parse_header_fields(input: &[u8]) -> Result<(Headers, &[u8]), ParseError> {
if input.starts_with(b"\r\n") {
return Ok((Headers::new(), input.get(2..).unwrap_or(&[])));
}
if input.starts_with(b"\n") {
return Ok((Headers::new(), input.get(1..).unwrap_or(&[])));
}
let mut buf = BytesMut::with_capacity(64);
let mut spans = Vec::with_capacity(8);
let remaining = for_each_header_field(input, |h| {
push_span(&mut buf, &mut spans, h);
Ok(())
})?;
if spans.is_empty() {
return Ok((Headers::new(), remaining));
}
Ok((Headers::from_spans(buf.freeze(), spans), remaining))
}
#[inline]
fn usize_as_u32(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}
#[inline]
fn push_span(
buf: &mut BytesMut,
spans: &mut Vec<(u32, u32, u32, u32)>,
h: HeaderRef<'_>,
) {
let name_start = usize_as_u32(buf.len());
buf.extend_from_slice(h.name);
let name_len = usize_as_u32(h.name.len());
let value_start = usize_as_u32(buf.len());
let value_len = if h.value.is_ascii() {
buf.extend_from_slice(h.value);
usize_as_u32(h.value.len())
} else {
let lossy = String::from_utf8_lossy(h.value);
buf.extend_from_slice(lossy.as_bytes());
usize_as_u32(lossy.len())
};
spans.push((name_start, name_len, value_start, value_len));
}
fn for_each_header_field<'a, F>(
input: &'a [u8],
mut on_field: F,
) -> Result<&'a [u8], ParseError>
where
F: FnMut(HeaderRef<'a>) -> Result<(), ParseError>,
{
let mut remaining = input;
if !remaining.is_empty() {
let first_byte = remaining.first().copied();
if first_byte == Some(b' ') || first_byte == Some(b'\t') {
return Err(ParseError::WhitespaceBeforeHeaders);
}
}
loop {
let byte0 = remaining.first().copied();
let byte1 = remaining.get(1).copied();
if remaining.len() >= 2 && byte0 == Some(b'\r') && byte1 == Some(b'\n') {
remaining = remaining.get(2..).ok_or(ParseError::MissingCrlf)?;
break;
}
if !remaining.is_empty() && byte0 == Some(b'\n') {
remaining = remaining.get(1..).ok_or(ParseError::MissingCrlf)?;
break;
}
let Some(colon_pos) = remaining.iter().position(|&b| b == b':') else {
return Err(ParseError::InvalidHeaderName);
};
if colon_pos == 0 {
return Err(ParseError::InvalidHeaderName);
}
let name_bytes = remaining
.get(..colon_pos)
.ok_or(ParseError::InvalidHeaderName)?;
for &b in name_bytes {
if !is_token_char(b) {
return Err(ParseError::InvalidHeaderName);
}
}
remaining = remaining
.get(colon_pos + 1..)
.ok_or(ParseError::InvalidHeaderValue)?;
while !remaining.is_empty() {
let first_byte = remaining.first().copied();
if first_byte == Some(b' ') || first_byte == Some(b'\t') {
remaining = remaining.get(1..).ok_or(ParseError::InvalidHeaderValue)?;
} else {
break;
}
}
let line_end = remaining
.iter()
.position(|&b| b == b'\r' || b == b'\n')
.unwrap_or(remaining.len());
let mut value_slice = remaining
.get(..line_end)
.ok_or(ParseError::InvalidHeaderValue)?;
while let Some((&last, rest)) = value_slice.split_last() {
if last == b' ' || last == b'\t' {
value_slice = rest;
} else {
break;
}
}
for &b in value_slice {
if !is_field_vchar_or_ws(b) {
return Err(ParseError::InvalidHeaderValue);
}
}
remaining = remaining.get(line_end..).ok_or(ParseError::MissingCrlf)?;
let next_byte0 = remaining.first().copied();
let next_byte1 = remaining.get(1).copied();
let next_byte2 = remaining.get(2).copied();
if remaining.len() >= 3
&& next_byte0 == Some(b'\r')
&& next_byte1 == Some(b'\n')
&& (next_byte2 == Some(b' ') || next_byte2 == Some(b'\t'))
{
return Err(ParseError::ObsoleteFoldInHeader);
}
if remaining.len() >= 2 && next_byte0 == Some(b'\n') && (next_byte1 == Some(b' ') || next_byte1 == Some(b'\t')) {
return Err(ParseError::ObsoleteFoldInHeader);
}
if remaining.len() >= 2 && next_byte0 == Some(b'\r') && next_byte1 == Some(b'\n') {
remaining = remaining.get(2..).ok_or(ParseError::MissingCrlf)?;
} else if !remaining.is_empty() && next_byte0 == Some(b'\n') {
remaining = remaining.get(1..).ok_or(ParseError::MissingCrlf)?;
} else {
return Err(ParseError::MissingCrlf);
}
on_field(HeaderRef {
name: name_bytes,
value: value_slice,
})?;
}
Ok(remaining)
}
#[inline]
pub const fn is_token_char(b: u8) -> bool {
matches!(
b,
b'!'
| b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'0'..=b'9'
| b'A'..=b'Z'
| b'^'
| b'_'
| b'`'
| b'a'..=b'z'
| b'|'
| b'~'
)
}
#[inline]
const fn is_field_vchar_or_ws(b: u8) -> bool {
matches!(b, 0x09 | 0x20..=0x7e | 0x80..=0xff)
}
pub fn expect_crlf(input: &[u8]) -> Result<&[u8], ParseError> {
if input.is_empty() {
return Err(ParseError::MissingCrlf);
}
let byte0 = input.first().copied();
let byte1 = input.get(1).copied();
if byte0 == Some(b'\r') && byte1 == Some(b'\n') {
return input.get(2..).ok_or(ParseError::MissingCrlf);
}
if byte0 == Some(b'\n') {
return input.get(1..).ok_or(ParseError::MissingCrlf);
}
if byte0 == Some(b'\r') && byte1.is_none() {
return Err(ParseError::MissingCrlf);
}
if byte0 == Some(b'\r') {
return Err(ParseError::BareCarriageReturn);
}
Err(ParseError::MissingCrlf)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use bytes::Bytes;
#[test]
fn try_wire_spans_maps_subslices() {
let section = b"Host: example.com\r\nX-A: 1\r\n\r\n";
let (refs, rest) = scan_header_fields(section).unwrap();
assert!(rest.is_empty());
let spans = try_wire_spans(section, &refs).expect("ascii");
let headers = Headers::from_spans(Bytes::copy_from_slice(section), spans);
assert_eq!(headers.get("host"), Some("example.com"));
assert_eq!(headers.get("x-a"), Some("1"));
assert!(headers.arena_len() > "Host".len() + "example.com".len() + "X-A".len() + "1".len());
}
#[test]
fn try_wire_spans_rejects_obs_text() {
let section = b"X-Bin: \xff\xfe\r\n\r\n";
let (refs, _) = scan_header_fields(section).unwrap();
assert!(try_wire_spans(section, &refs).is_none());
let copied = materialize_headers(&refs);
assert_eq!(copied.get("x-bin"), Some("\u{fffd}\u{fffd}"));
}
#[test]
fn try_wire_spans_trims_ows_offsets() {
let section = b"Host: example.com \r\n\r\n";
let (refs, _) = scan_header_fields(section).unwrap();
let spans = try_wire_spans(section, &refs).unwrap();
let headers = Headers::from_spans(Bytes::copy_from_slice(section), spans);
assert_eq!(headers.get("host"), Some("example.com"));
}
}