use super::byte::is_ascii_ws;
use super::flags::{Flags, SeenHeaderHandler};
use crate::error::{Error, Result};
use crate::http::codec;
use crate::utils::bytes::{Ascii, Word};
pub const CSV_CLOSE_BIT: u8 = 1 << 0;
pub const CSV_KEEP_ALIVE_BIT: u8 = 1 << 1;
pub const CSV_CHUNKED_BIT: u8 = 1 << 2;
pub const CSV_CONTINUE_BIT: u8 = 1 << 3;
struct ContentLengthHeader;
struct TransferEncodingHeader;
struct ExpectHeader;
struct ConnHeader;
struct HostSeenHeader;
impl SeenHeaderHandler for ContentLengthHeader {
const SEEN_BIT: u16 = Flags::SEEN_CONTENT_LENGTH;
const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: ContentLength";
}
impl SeenHeaderHandler for TransferEncodingHeader {
const SEEN_BIT: u16 = Flags::SEEN_TRANSFER_ENCODING;
const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: TransferEncoding";
}
impl SeenHeaderHandler for ExpectHeader {
const SEEN_BIT: u16 = Flags::SEEN_EXPECT;
const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Expect";
}
impl SeenHeaderHandler for ConnHeader {
const SEEN_BIT: u16 = Flags::SEEN_CONNECTION;
const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Connection";
}
impl SeenHeaderHandler for HostSeenHeader {
const SEEN_BIT: u16 = Flags::SEEN_HOST;
const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Host";
}
trait KnownHeaderImpl: SeenHeaderHandler + Sized {
type Parsed;
fn parse_value(value: &[u8]) -> Result<Self::Parsed>;
fn parse_line(raw: &[u8]) -> Result<(Self::Parsed, usize)>;
fn apply_parsed(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
parsed: Self::Parsed,
) -> Result<()>;
fn dispatch_value(scan: &mut codec::HeaderScan, flags: &mut Flags, value: &[u8]) -> Result<()> {
flags.mark_seen::<Self>()?;
let parsed = Self::parse_value(value)?;
Self::apply_parsed(scan, flags, parsed)
}
fn dispatch_line(scan: &mut codec::HeaderScan, flags: &mut Flags, raw: &[u8]) -> Result<usize> {
flags.mark_seen::<Self>()?;
let (parsed, value_len) = Self::parse_line(raw)?;
Self::apply_parsed(scan, flags, parsed)?;
Ok(value_len)
}
}
impl KnownHeaderImpl for ContentLengthHeader {
type Parsed = usize;
fn parse_value(value: &[u8]) -> Result<usize> {
Ascii::parse_usize(value).ok_or_else(invalid_content_length)
}
fn parse_line(raw: &[u8]) -> Result<(usize, usize)> {
parse_content_length_line(raw)
}
fn apply_parsed(scan: &mut codec::HeaderScan, _flags: &mut Flags, len: usize) -> Result<()> {
if let Some(existing) = scan.content_length
&& existing != len
{
return Err(Error::BadRequest(
"Multiple Content-Length headers are not allowed".into(),
));
}
scan.content_length = Some(len);
Ok(())
}
}
impl KnownHeaderImpl for TransferEncodingHeader {
type Parsed = u8;
fn parse_value(value: &[u8]) -> Result<u8> {
Ok(parse_csv(value, CSV_CHUNKED_BIT))
}
fn parse_line(raw: &[u8]) -> Result<(u8, usize)> {
Ok(parse_csv_line(raw, CSV_CHUNKED_BIT))
}
fn apply_parsed(scan: &mut codec::HeaderScan, _flags: &mut Flags, found: u8) -> Result<()> {
scan.has_transfer_encoding = true;
if (found & CSV_CHUNKED_BIT) != 0 {
scan.is_chunked_transfer = true;
}
Ok(())
}
}
impl KnownHeaderImpl for ExpectHeader {
type Parsed = u8;
fn parse_value(value: &[u8]) -> Result<u8> {
Ok(parse_csv(value, CSV_CONTINUE_BIT))
}
fn parse_line(raw: &[u8]) -> Result<(u8, usize)> {
Ok(parse_csv_line(raw, CSV_CONTINUE_BIT))
}
fn apply_parsed(scan: &mut codec::HeaderScan, _flags: &mut Flags, found: u8) -> Result<()> {
scan.has_expect = true;
if (found & CSV_CONTINUE_BIT) != 0 {
scan.expect_continue = true;
}
Ok(())
}
}
impl KnownHeaderImpl for HostSeenHeader {
type Parsed = ();
fn parse_value(_value: &[u8]) -> Result<()> {
Ok(())
}
fn parse_line(raw: &[u8]) -> Result<((), usize)> {
Ok(((), trim_len(raw)))
}
fn apply_parsed(_scan: &mut codec::HeaderScan, flags: &mut Flags, _: ()) -> Result<()> {
flags.set(Flags::HAS_HOST);
Ok(())
}
}
impl KnownHeaderImpl for ConnHeader {
type Parsed = u8;
fn parse_value(value: &[u8]) -> Result<u8> {
Ok(parse_csv(value, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT))
}
fn parse_line(raw: &[u8]) -> Result<(u8, usize)> {
Ok(parse_csv_line(raw, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT))
}
fn apply_parsed(_scan: &mut codec::HeaderScan, flags: &mut Flags, found: u8) -> Result<()> {
if (found & CSV_CLOSE_BIT) != 0 {
flags.set(Flags::CONNECTION_CLOSE);
}
if (found & CSV_KEEP_ALIVE_BIT) != 0 {
flags.set(Flags::CONNECTION_KEEP_ALIVE);
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub enum KnownHeader {
ContentLength,
TransferEncoding,
Expect,
Host,
Connection,
}
impl KnownHeader {
pub fn from_name(name: &[u8]) -> Option<Self> {
match name.len() {
4 if name.eq_ignore_ascii_case(b"host") => Some(Self::Host),
6 if name.eq_ignore_ascii_case(b"expect") => Some(Self::Expect),
10 if name.eq_ignore_ascii_case(b"connection") => Some(Self::Connection),
14 if name.eq_ignore_ascii_case(b"content-length") => Some(Self::ContentLength),
17 if name.eq_ignore_ascii_case(b"transfer-encoding") => Some(Self::TransferEncoding),
_ => None,
}
}
pub fn apply(
self,
scan: &mut codec::HeaderScan,
flags: &mut Flags,
value: &[u8],
) -> Result<()> {
match self {
Self::ContentLength => ContentLengthHeader::dispatch_value(scan, flags, value),
Self::TransferEncoding => TransferEncodingHeader::dispatch_value(scan, flags, value),
Self::Expect => ExpectHeader::dispatch_value(scan, flags, value),
Self::Host => HostSeenHeader::dispatch_value(scan, flags, value),
Self::Connection => ConnHeader::dispatch_value(scan, flags, value),
}
}
pub fn apply_line(
self,
scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<usize> {
match self {
Self::ContentLength => ContentLengthHeader::dispatch_line(scan, flags, raw),
Self::TransferEncoding => TransferEncodingHeader::dispatch_line(scan, flags, raw),
Self::Expect => ExpectHeader::dispatch_line(scan, flags, raw),
Self::Host => HostSeenHeader::dispatch_line(scan, flags, raw),
Self::Connection => ConnHeader::dispatch_line(scan, flags, raw),
}
}
}
pub fn apply_host(scan: &mut codec::HeaderScan, flags: &mut Flags) -> Result<()> {
KnownHeader::Host.apply(scan, flags, b"")
}
pub fn apply_expect(scan: &mut codec::HeaderScan, flags: &mut Flags, value: &[u8]) -> Result<()> {
KnownHeader::Expect.apply(scan, flags, value)
}
pub fn apply_connection(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
value: &[u8],
) -> Result<()> {
KnownHeader::Connection.apply(scan, flags, value)
}
pub fn apply_content_length(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
value: &[u8],
) -> Result<()> {
KnownHeader::ContentLength.apply(scan, flags, value)
}
pub fn apply_transfer_encoding(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
value: &[u8],
) -> Result<()> {
KnownHeader::TransferEncoding.apply(scan, flags, value)
}
pub fn apply_accept_encoding(
scan: &mut codec::HeaderScan,
_flags: &mut Flags,
raw: &[u8],
) -> Result<()> {
let mut start = 0usize;
while start <= raw.len() {
let end = raw[start..]
.iter()
.position(|&b| b == b',')
.map(|p| start + p)
.unwrap_or(raw.len());
let mut vs = start;
let mut ve = end;
while vs < ve && is_ascii_ws(raw[vs]) {
vs += 1;
}
while ve > vs && is_ascii_ws(raw[ve - 1]) {
ve -= 1;
}
if vs < ve {
let main = ae_token_main(&raw[vs..ve]);
if main.eq_ignore_ascii_case(b"gzip") {
scan.accept_encoding_gzip = true;
return Ok(());
}
}
if end >= raw.len() {
break;
}
start = end + 1;
}
Ok(())
}
pub fn host_line(
_scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
let Some((tail_end, value_start, value_end)) = trim_line(raw)? else {
return Ok(None);
};
flags.mark_seen::<HostSeenHeader>()?;
flags.set(Flags::HAS_HOST);
Ok(Some((tail_end, value_start, value_end)))
}
pub fn expect_line(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
if let Some(out) = expect_fast_continue(raw, scan, flags)? {
return Ok(Some(out));
}
let Some((found, tail_end, value_start, value_end)) = csv_line(raw, CSV_CONTINUE_BIT)? else {
return Ok(None);
};
flags.mark_seen::<ExpectHeader>()?;
scan.has_expect = true;
if (found & CSV_CONTINUE_BIT) != 0 {
scan.expect_continue = true;
}
Ok(Some((tail_end, value_start, value_end)))
}
fn try_token_line_lit<const N: usize>(raw: &[u8], expected: &[u8; N]) -> Option<(usize, usize)> {
let value_start = if raw.first() == Some(&b' ') { 1 } else { 0 };
let body_end = value_start + N;
if raw.len() < body_end + 2 {
return None;
}
if !Word::swar_eq_ci::<N>(&raw[value_start..], expected) {
return None;
}
if raw[body_end] != b'\r' || raw[body_end + 1] != b'\n' {
return None;
}
Some((value_start, body_end))
}
fn expect_fast_continue(
raw: &[u8],
scan: &mut codec::HeaderScan,
flags: &mut Flags,
) -> Result<Option<(usize, usize, usize)>> {
let Some((value_start, body_end)) = try_token_line_lit(raw, b"100-continue") else {
return Ok(None);
};
flags.mark_seen::<ExpectHeader>()?;
scan.has_expect = true;
scan.expect_continue = true;
Ok(Some((body_end, value_start, body_end)))
}
pub fn conn_line(
_scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
if let Some(out) =
conn_fast_token::<10, { Flags::CONNECTION_KEEP_ALIVE }>(raw, b"keep-alive", flags)?
{
return Ok(Some(out));
}
if let Some(out) = conn_fast_token::<5, { Flags::CONNECTION_CLOSE }>(raw, b"close", flags)? {
return Ok(Some(out));
}
let Some((found, tail_end, value_start, value_end)) =
csv_line(raw, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT)?
else {
return Ok(None);
};
flags.mark_seen::<ConnHeader>()?;
if (found & CSV_CLOSE_BIT) != 0 {
flags.set(Flags::CONNECTION_CLOSE);
}
if (found & CSV_KEEP_ALIVE_BIT) != 0 {
flags.set(Flags::CONNECTION_KEEP_ALIVE);
}
Ok(Some((tail_end, value_start, value_end)))
}
fn conn_fast_token<const N: usize, const FLAG: u16>(
raw: &[u8],
expected: &[u8; N],
flags: &mut Flags,
) -> Result<Option<(usize, usize, usize)>> {
let Some((value_start, body_end)) = try_token_line_lit(raw, expected) else {
return Ok(None);
};
flags.mark_seen::<ConnHeader>()?;
flags.set(FLAG);
Ok(Some((body_end, value_start, body_end)))
}
fn clen_fast(
raw: &[u8],
scan: &mut codec::HeaderScan,
flags: &mut Flags,
) -> Result<Option<(usize, usize, usize)>> {
let value_start = if raw.first() == Some(&b' ') { 1 } else { 0 };
if raw.len() <= value_start + 2 {
return Ok(None);
}
let mut acc: usize = 0;
let mut idx = value_start;
while idx < raw.len() {
let b = raw[idx];
if !b.is_ascii_digit() {
break;
}
let Some(next) = acc
.checked_mul(10)
.and_then(|m| m.checked_add((b - b'0') as usize))
else {
return Ok(None);
};
acc = next;
idx += 1;
}
if idx == value_start {
return Ok(None);
}
let value_end = idx;
if value_end + 1 >= raw.len() {
return Ok(None);
}
if raw[value_end] != b'\r' || raw[value_end + 1] != b'\n' {
return Ok(None);
}
flags.mark_seen::<ContentLengthHeader>()?;
if let Some(existing) = scan.content_length
&& existing != acc
{
return Err(Error::BadRequest(
"Multiple Content-Length headers are not allowed".into(),
));
}
scan.content_length = Some(acc);
Ok(Some((value_end, value_start, value_end)))
}
pub fn clen_line(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
if let Some(out) = clen_fast(raw, scan, flags)? {
return Ok(Some(out));
}
let Some((len, tail_end, value_start, value_end)) = parse_clen(raw)? else {
return Ok(None);
};
flags.mark_seen::<ContentLengthHeader>()?;
if let Some(existing) = scan.content_length
&& existing != len
{
return Err(Error::BadRequest(
"Multiple Content-Length headers are not allowed".into(),
));
}
scan.content_length = Some(len);
Ok(Some((tail_end, value_start, value_end)))
}
pub fn te_line(
scan: &mut codec::HeaderScan,
flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
if let Some(out) = te_fast_chunked(raw, scan, flags)? {
return Ok(Some(out));
}
let Some((found, tail_end, value_start, value_end)) = csv_line(raw, CSV_CHUNKED_BIT)? else {
return Ok(None);
};
flags.mark_seen::<TransferEncodingHeader>()?;
scan.has_transfer_encoding = true;
if (found & CSV_CHUNKED_BIT) != 0 {
scan.is_chunked_transfer = true;
}
Ok(Some((tail_end, value_start, value_end)))
}
fn te_fast_chunked(
raw: &[u8],
scan: &mut codec::HeaderScan,
flags: &mut Flags,
) -> Result<Option<(usize, usize, usize)>> {
let Some((value_start, body_end)) = try_token_line_lit(raw, b"chunked") else {
return Ok(None);
};
flags.mark_seen::<TransferEncodingHeader>()?;
scan.has_transfer_encoding = true;
scan.is_chunked_transfer = true;
Ok(Some((body_end, value_start, body_end)))
}
pub(super) fn ae_line(
scan: &mut codec::HeaderScan,
_flags: &mut Flags,
raw: &[u8],
) -> Result<Option<(usize, usize, usize)>> {
let Some((found, tail_end, value_start, value_end)) = ae_csv_line(raw)? else {
return Ok(None);
};
if found {
scan.accept_encoding_gzip = true;
}
Ok(Some((tail_end, value_start, value_end)))
}
fn ae_csv_line(raw: &[u8]) -> Result<Option<(bool, usize, usize, usize)>> {
walk_csv_line(raw, false, |found, token| {
found || token_eq_ci(ae_token_main(token), b"gzip")
})
}
fn ae_token_main(token: &[u8]) -> &[u8] {
let mut end = 0usize;
while end < token.len() {
let b = token[end];
if b == b';' || is_ascii_ws(b) {
break;
}
end += 1;
}
&token[..end]
}
fn invalid_content_length() -> Error {
Error::BadRequest("Invalid Content-Length".into())
}
fn invalid_header_value() -> Error {
Error::BadRequest("Invalid header value".into())
}
fn scan_content_length_value(raw: &[u8]) -> Result<Option<(usize, usize, usize)>> {
let mut idx = 0usize;
while idx < raw.len() && is_ascii_ws(raw[idx]) {
idx += 1;
}
let start = idx;
if start == raw.len() {
return Ok(None);
}
while idx < raw.len() && raw[idx].is_ascii_digit() {
idx += 1;
}
if idx == start {
return Err(invalid_content_length());
}
let end = idx;
let value = Ascii::parse_usize(&raw[start..end]).ok_or_else(invalid_content_length)?;
Ok(Some((value, start, end)))
}
struct CsvScan<'a> {
raw: &'a [u8],
pos: usize,
}
impl<'a> CsvScan<'a> {
fn new(raw: &'a [u8]) -> Self {
Self { raw, pos: 0 }
}
}
impl<'a> Iterator for CsvScan<'a> {
type Item = (&'a [u8], usize);
fn next(&mut self) -> Option<Self::Item> {
if self.pos > self.raw.len() {
return None;
}
while self.pos < self.raw.len() && is_ascii_ws(self.raw[self.pos]) {
self.pos += 1;
}
let start = self.pos;
while self.pos < self.raw.len() && self.raw[self.pos] != b',' {
self.pos += 1;
}
let end = self.pos;
let mut stop = end;
while stop > start && is_ascii_ws(self.raw[stop - 1]) {
stop -= 1;
}
self.pos = end.saturating_add(1);
if start > end {
return None;
}
Some((&self.raw[start..stop], stop))
}
}
fn parse_csv(raw: &[u8], want: u8) -> u8 {
let mut found = 0u8;
for (token, _) in CsvScan::new(raw) {
if found == want {
break;
}
found |= match_csv_token(token, want);
}
found
}
fn trim_len(raw: &[u8]) -> usize {
let mut start = 0usize;
while start < raw.len() && is_ascii_ws(raw[start]) {
start += 1;
}
let mut end = raw.len();
while end > start && is_ascii_ws(raw[end - 1]) {
end -= 1;
}
end - start
}
fn trim_line(raw: &[u8]) -> Result<Option<(usize, usize, usize)>> {
let mut idx = 0usize;
while idx < raw.len() && is_ascii_ws(raw[idx]) {
idx += 1;
}
let value_start = idx;
let mut value_end = value_start;
loop {
if idx >= raw.len() {
return Ok(None);
}
let b = raw[idx];
if b == b'\r' {
if idx + 1 >= raw.len() {
return Ok(None);
}
if raw[idx + 1] != b'\n' {
return Err(invalid_header_value());
}
return Ok(Some((idx, value_start, value_end)));
}
if b == b'\n' {
return Err(invalid_header_value());
}
if !is_ascii_ws(b) {
value_end = idx + 1;
}
idx += 1;
}
}
fn parse_content_length_line(raw: &[u8]) -> Result<(usize, usize)> {
let (value, start, end) = scan_content_length_value(raw)?.ok_or_else(invalid_content_length)?;
let mut idx = end;
while idx < raw.len() && is_ascii_ws(raw[idx]) {
idx += 1;
}
if idx != raw.len() {
return Err(invalid_content_length());
}
Ok((value, end - start))
}
fn parse_clen(raw: &[u8]) -> Result<Option<(usize, usize, usize, usize)>> {
let Some((value, value_start, value_end)) = scan_content_length_value(raw)? else {
return Ok(None);
};
let mut idx = value_end;
while idx < raw.len() && is_ascii_ws(raw[idx]) {
idx += 1;
}
if idx >= raw.len() {
return Ok(None);
}
if raw[idx] != b'\r' {
return Err(invalid_content_length());
}
if idx + 1 >= raw.len() {
return Ok(None);
}
if raw[idx + 1] != b'\n' {
return Err(invalid_content_length());
}
Ok(Some((value, idx, value_start, value_end)))
}
fn trim_left(raw: &[u8]) -> usize {
let mut start = 0usize;
while start < raw.len() && is_ascii_ws(raw[start]) {
start += 1;
}
start
}
fn token_eq_ci<const N: usize>(token: &[u8], expected: &[u8; N]) -> bool {
token.len() == N && Word::swar_eq_ci::<N>(token, expected)
}
fn match_csv_token(token: &[u8], want: u8) -> u8 {
if (want & CSV_CLOSE_BIT) != 0 && token_eq_ci(token, b"close") {
return CSV_CLOSE_BIT;
}
if (want & CSV_KEEP_ALIVE_BIT) != 0 && token_eq_ci(token, b"keep-alive") {
return CSV_KEEP_ALIVE_BIT;
}
if (want & CSV_CHUNKED_BIT) != 0 && token_eq_ci(token, b"chunked") {
return CSV_CHUNKED_BIT;
}
if (want & CSV_CONTINUE_BIT) != 0 && token_eq_ci(token, b"100-continue") {
return CSV_CONTINUE_BIT;
}
0
}
fn parse_csv_line(raw: &[u8], want: u8) -> (u8, usize) {
let mut found = 0u8;
let mut value_len = 0usize;
for (token, stop) in CsvScan::new(raw) {
if found == want {
break;
}
if !token.is_empty() {
value_len = stop;
found |= match_csv_token(token, want);
}
}
(found, value_len.saturating_sub(trim_left(raw)))
}
fn csv_line(raw: &[u8], want: u8) -> Result<Option<(u8, usize, usize, usize)>> {
walk_csv_line(raw, 0u8, |found, token| {
found | match_csv_token(token, want)
})
}
fn walk_csv_line<A: Copy, F: FnMut(A, &[u8]) -> A>(
raw: &[u8],
init: A,
mut fold: F,
) -> Result<Option<(A, usize, usize, usize)>> {
let mut acc = init;
let mut idx = 0usize;
let mut first_non_ws: Option<usize> = None;
let mut last_non_ws = 0usize;
loop {
let Some(rel) = memchr::memchr3(b',', b'\r', b'\n', &raw[idx..]) else {
return Ok(None);
};
let end = idx + rel;
if raw[end] == b'\n' {
return Err(invalid_header_value());
}
let mut t_lo = idx;
while t_lo < end && is_ascii_ws(raw[t_lo]) {
t_lo += 1;
}
let mut t_hi = end;
while t_hi > t_lo && is_ascii_ws(raw[t_hi - 1]) {
t_hi -= 1;
}
if t_hi > t_lo {
if first_non_ws.is_none() {
first_non_ws = Some(t_lo);
}
last_non_ws = t_hi;
acc = fold(acc, &raw[t_lo..t_hi]);
}
if raw[end] == b'\r' {
if end + 1 >= raw.len() {
return Ok(None);
}
if raw[end + 1] != b'\n' {
return Err(invalid_header_value());
}
let value_start = first_non_ws.unwrap_or(0);
let value_end = if first_non_ws.is_some() {
last_non_ws
} else {
value_start
};
return Ok(Some((acc, end, value_start, value_end)));
}
idx = end + 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partial_known_header_lines_do_not_commit_seen_state() {
let mut scan = codec::HeaderScan {
content_length: None,
has_transfer_encoding: false,
is_chunked_transfer: false,
has_expect: false,
expect_continue: false,
duplicate_content_length: false,
accept_encoding_gzip: false,
};
let mut flags = Flags::default();
assert!(matches!(
host_line(&mut scan, &mut flags, b" localhost"),
Ok(None)
));
assert!(!flags.has(Flags::SEEN_HOST));
assert!(!flags.has(Flags::HAS_HOST));
assert!(matches!(
conn_line(&mut scan, &mut flags, b" keep-alive"),
Ok(None)
));
assert!(!flags.has(Flags::SEEN_CONNECTION));
assert!(!flags.has(Flags::CONNECTION_KEEP_ALIVE));
assert!(!flags.has(Flags::CONNECTION_CLOSE));
assert!(matches!(
clen_line(&mut scan, &mut flags, b" 123"),
Ok(None)
));
assert!(!flags.has(Flags::SEEN_CONTENT_LENGTH));
assert!(scan.content_length.is_none());
assert!(matches!(
te_line(&mut scan, &mut flags, b" chunked"),
Ok(None)
));
assert!(!flags.has(Flags::SEEN_TRANSFER_ENCODING));
assert!(!scan.has_transfer_encoding);
assert!(!scan.is_chunked_transfer);
assert!(matches!(
expect_line(&mut scan, &mut flags, b" 100-continue"),
Ok(None)
));
assert!(!flags.has(Flags::SEEN_EXPECT));
assert!(!scan.has_expect);
assert!(!scan.expect_continue);
}
}