Skip to main content

sark_core/http/head/
apply.rs

1use super::byte::is_ascii_ws;
2use super::flags::{Flags, SeenHeaderHandler};
3use crate::error::{Error, Result};
4use crate::http::codec;
5use crate::utils::bytes::{Ascii, Word};
6
7pub const CSV_CLOSE_BIT: u8 = 1 << 0;
8pub const CSV_KEEP_ALIVE_BIT: u8 = 1 << 1;
9pub const CSV_CHUNKED_BIT: u8 = 1 << 2;
10pub const CSV_CONTINUE_BIT: u8 = 1 << 3;
11
12struct ContentLengthHeader;
13struct TransferEncodingHeader;
14struct ExpectHeader;
15struct ConnHeader;
16struct HostSeenHeader;
17
18impl SeenHeaderHandler for ContentLengthHeader {
19    const SEEN_BIT: u16 = Flags::SEEN_CONTENT_LENGTH;
20    const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: ContentLength";
21}
22
23impl SeenHeaderHandler for TransferEncodingHeader {
24    const SEEN_BIT: u16 = Flags::SEEN_TRANSFER_ENCODING;
25    const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: TransferEncoding";
26}
27
28impl SeenHeaderHandler for ExpectHeader {
29    const SEEN_BIT: u16 = Flags::SEEN_EXPECT;
30    const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Expect";
31}
32
33impl SeenHeaderHandler for ConnHeader {
34    const SEEN_BIT: u16 = Flags::SEEN_CONNECTION;
35    const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Connection";
36}
37
38impl SeenHeaderHandler for HostSeenHeader {
39    const SEEN_BIT: u16 = Flags::SEEN_HOST;
40    const DUPLICATE_ERR: &'static str = "Duplicate header not allowed: Host";
41}
42
43trait KnownHeaderImpl: SeenHeaderHandler + Sized {
44    type Parsed;
45
46    fn parse_value(value: &[u8]) -> Result<Self::Parsed>;
47    fn parse_line(raw: &[u8]) -> Result<(Self::Parsed, usize)>;
48    fn apply_parsed(
49        scan: &mut codec::HeaderScan,
50        flags: &mut Flags,
51        parsed: Self::Parsed,
52    ) -> Result<()>;
53
54    fn dispatch_value(scan: &mut codec::HeaderScan, flags: &mut Flags, value: &[u8]) -> Result<()> {
55        flags.mark_seen::<Self>()?;
56        let parsed = Self::parse_value(value)?;
57        Self::apply_parsed(scan, flags, parsed)
58    }
59
60    fn dispatch_line(scan: &mut codec::HeaderScan, flags: &mut Flags, raw: &[u8]) -> Result<usize> {
61        flags.mark_seen::<Self>()?;
62        let (parsed, value_len) = Self::parse_line(raw)?;
63        Self::apply_parsed(scan, flags, parsed)?;
64        Ok(value_len)
65    }
66}
67
68impl KnownHeaderImpl for ContentLengthHeader {
69    type Parsed = usize;
70
71    fn parse_value(value: &[u8]) -> Result<usize> {
72        Ascii::parse_usize(value).ok_or_else(invalid_content_length)
73    }
74    fn parse_line(raw: &[u8]) -> Result<(usize, usize)> {
75        parse_content_length_line(raw)
76    }
77    fn apply_parsed(scan: &mut codec::HeaderScan, _flags: &mut Flags, len: usize) -> Result<()> {
78        if let Some(existing) = scan.content_length
79            && existing != len
80        {
81            return Err(Error::BadRequest(
82                "Multiple Content-Length headers are not allowed".into(),
83            ));
84        }
85        scan.content_length = Some(len);
86        Ok(())
87    }
88}
89
90impl KnownHeaderImpl for TransferEncodingHeader {
91    type Parsed = bool;
92
93    fn parse_value(value: &[u8]) -> Result<bool> {
94        te_request_chunked_final(value)
95    }
96    fn parse_line(raw: &[u8]) -> Result<(bool, usize)> {
97        match trim_line(raw)? {
98            Some((_tail_end, value_start, value_end)) => {
99                let is_chunked = te_request_chunked_final(&raw[value_start..value_end])?;
100                Ok((is_chunked, value_end - value_start))
101            }
102            None => {
103                let is_chunked = te_request_chunked_final(raw)?;
104                Ok((is_chunked, raw.len()))
105            }
106        }
107    }
108    fn apply_parsed(
109        scan: &mut codec::HeaderScan,
110        _flags: &mut Flags,
111        is_chunked: bool,
112    ) -> Result<()> {
113        scan.has_transfer_encoding = true;
114        if is_chunked {
115            scan.is_chunked_transfer = true;
116        }
117        Ok(())
118    }
119}
120
121impl KnownHeaderImpl for ExpectHeader {
122    type Parsed = u8;
123
124    fn parse_value(value: &[u8]) -> Result<u8> {
125        Ok(parse_csv(value, CSV_CONTINUE_BIT))
126    }
127    fn parse_line(raw: &[u8]) -> Result<(u8, usize)> {
128        Ok(parse_csv_line(raw, CSV_CONTINUE_BIT))
129    }
130    fn apply_parsed(scan: &mut codec::HeaderScan, _flags: &mut Flags, found: u8) -> Result<()> {
131        scan.has_expect = true;
132        if (found & CSV_CONTINUE_BIT) != 0 {
133            scan.expect_continue = true;
134        }
135        Ok(())
136    }
137}
138
139impl KnownHeaderImpl for HostSeenHeader {
140    type Parsed = ();
141
142    fn parse_value(_value: &[u8]) -> Result<()> {
143        Ok(())
144    }
145    fn parse_line(raw: &[u8]) -> Result<((), usize)> {
146        Ok(((), trim_len(raw)))
147    }
148    fn apply_parsed(_scan: &mut codec::HeaderScan, flags: &mut Flags, _: ()) -> Result<()> {
149        flags.set(Flags::HAS_HOST);
150        Ok(())
151    }
152}
153
154impl KnownHeaderImpl for ConnHeader {
155    type Parsed = u8;
156
157    fn parse_value(value: &[u8]) -> Result<u8> {
158        Ok(parse_csv(value, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT))
159    }
160    fn parse_line(raw: &[u8]) -> Result<(u8, usize)> {
161        Ok(parse_csv_line(raw, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT))
162    }
163    fn apply_parsed(_scan: &mut codec::HeaderScan, flags: &mut Flags, found: u8) -> Result<()> {
164        if (found & CSV_CLOSE_BIT) != 0 {
165            flags.set(Flags::CONNECTION_CLOSE);
166        }
167        if (found & CSV_KEEP_ALIVE_BIT) != 0 {
168            flags.set(Flags::CONNECTION_KEEP_ALIVE);
169        }
170        Ok(())
171    }
172}
173
174#[derive(Clone, Copy)]
175pub enum KnownHeader {
176    ContentLength,
177    TransferEncoding,
178    Expect,
179    Host,
180    Connection,
181}
182
183impl KnownHeader {
184    pub fn from_name(name: &[u8]) -> Option<Self> {
185        match name.len() {
186            4 if name.eq_ignore_ascii_case(b"host") => Some(Self::Host),
187            6 if name.eq_ignore_ascii_case(b"expect") => Some(Self::Expect),
188            10 if name.eq_ignore_ascii_case(b"connection") => Some(Self::Connection),
189            14 if name.eq_ignore_ascii_case(b"content-length") => Some(Self::ContentLength),
190            17 if name.eq_ignore_ascii_case(b"transfer-encoding") => Some(Self::TransferEncoding),
191            _ => None,
192        }
193    }
194
195    pub fn apply(
196        self,
197        scan: &mut codec::HeaderScan,
198        flags: &mut Flags,
199        value: &[u8],
200    ) -> Result<()> {
201        match self {
202            Self::ContentLength => ContentLengthHeader::dispatch_value(scan, flags, value),
203            Self::TransferEncoding => TransferEncodingHeader::dispatch_value(scan, flags, value),
204            Self::Expect => ExpectHeader::dispatch_value(scan, flags, value),
205            Self::Host => HostSeenHeader::dispatch_value(scan, flags, value),
206            Self::Connection => ConnHeader::dispatch_value(scan, flags, value),
207        }
208    }
209
210    pub fn apply_line(
211        self,
212        scan: &mut codec::HeaderScan,
213        flags: &mut Flags,
214        raw: &[u8],
215    ) -> Result<usize> {
216        match self {
217            Self::ContentLength => ContentLengthHeader::dispatch_line(scan, flags, raw),
218            Self::TransferEncoding => TransferEncodingHeader::dispatch_line(scan, flags, raw),
219            Self::Expect => ExpectHeader::dispatch_line(scan, flags, raw),
220            Self::Host => HostSeenHeader::dispatch_line(scan, flags, raw),
221            Self::Connection => ConnHeader::dispatch_line(scan, flags, raw),
222        }
223    }
224}
225
226pub fn apply_host(scan: &mut codec::HeaderScan, flags: &mut Flags) -> Result<()> {
227    KnownHeader::Host.apply(scan, flags, b"")
228}
229
230pub fn apply_expect(scan: &mut codec::HeaderScan, flags: &mut Flags, value: &[u8]) -> Result<()> {
231    KnownHeader::Expect.apply(scan, flags, value)
232}
233
234pub fn apply_connection(
235    scan: &mut codec::HeaderScan,
236    flags: &mut Flags,
237    value: &[u8],
238) -> Result<()> {
239    KnownHeader::Connection.apply(scan, flags, value)
240}
241
242pub fn apply_content_length(
243    scan: &mut codec::HeaderScan,
244    flags: &mut Flags,
245    value: &[u8],
246) -> Result<()> {
247    KnownHeader::ContentLength.apply(scan, flags, value)
248}
249
250pub fn apply_transfer_encoding(
251    scan: &mut codec::HeaderScan,
252    flags: &mut Flags,
253    value: &[u8],
254) -> Result<()> {
255    KnownHeader::TransferEncoding.apply(scan, flags, value)
256}
257
258pub fn apply_accept_encoding(
259    scan: &mut codec::HeaderScan,
260    _flags: &mut Flags,
261    raw: &[u8],
262) -> Result<()> {
263    let mut start = 0usize;
264    while start <= raw.len() {
265        let end = raw[start..]
266            .iter()
267            .position(|&b| b == b',')
268            .map(|p| start + p)
269            .unwrap_or(raw.len());
270        let mut vs = start;
271        let mut ve = end;
272        while vs < ve && is_ascii_ws(raw[vs]) {
273            vs += 1;
274        }
275        while ve > vs && is_ascii_ws(raw[ve - 1]) {
276            ve -= 1;
277        }
278        if vs < ve {
279            let main = ae_token_main(&raw[vs..ve]);
280            if main.eq_ignore_ascii_case(b"gzip") {
281                scan.accept_encoding_gzip = true;
282                return Ok(());
283            }
284        }
285        if end >= raw.len() {
286            break;
287        }
288        start = end + 1;
289    }
290    Ok(())
291}
292
293pub fn host_line(
294    _scan: &mut codec::HeaderScan,
295    flags: &mut Flags,
296    raw: &[u8],
297) -> Result<Option<(usize, usize, usize)>> {
298    let Some((tail_end, value_start, value_end)) = trim_line(raw)? else {
299        return Ok(None);
300    };
301    flags.mark_seen::<HostSeenHeader>()?;
302    flags.set(Flags::HAS_HOST);
303    Ok(Some((tail_end, value_start, value_end)))
304}
305
306pub fn expect_line(
307    scan: &mut codec::HeaderScan,
308    flags: &mut Flags,
309    raw: &[u8],
310) -> Result<Option<(usize, usize, usize)>> {
311    if let Some(out) = expect_fast_continue(raw, scan, flags)? {
312        return Ok(Some(out));
313    }
314    let Some((found, tail_end, value_start, value_end)) = csv_line(raw, CSV_CONTINUE_BIT)? else {
315        return Ok(None);
316    };
317    flags.mark_seen::<ExpectHeader>()?;
318    scan.has_expect = true;
319    if (found & CSV_CONTINUE_BIT) != 0 {
320        scan.expect_continue = true;
321    }
322    Ok(Some((tail_end, value_start, value_end)))
323}
324
325fn try_token_line_lit<const N: usize>(raw: &[u8], expected: &[u8; N]) -> Option<(usize, usize)> {
326    let value_start = if raw.first() == Some(&b' ') { 1 } else { 0 };
327    let body_end = value_start + N;
328    if raw.len() < body_end + 2 {
329        return None;
330    }
331    if !Word::swar_eq_ci::<N>(&raw[value_start..], expected) {
332        return None;
333    }
334    if raw[body_end] != b'\r' || raw[body_end + 1] != b'\n' {
335        return None;
336    }
337    Some((value_start, body_end))
338}
339
340fn expect_fast_continue(
341    raw: &[u8],
342    scan: &mut codec::HeaderScan,
343    flags: &mut Flags,
344) -> Result<Option<(usize, usize, usize)>> {
345    let Some((value_start, body_end)) = try_token_line_lit(raw, b"100-continue") else {
346        return Ok(None);
347    };
348    flags.mark_seen::<ExpectHeader>()?;
349    scan.has_expect = true;
350    scan.expect_continue = true;
351    Ok(Some((body_end, value_start, body_end)))
352}
353
354pub fn conn_line(
355    _scan: &mut codec::HeaderScan,
356    flags: &mut Flags,
357    raw: &[u8],
358) -> Result<Option<(usize, usize, usize)>> {
359    if let Some(out) =
360        conn_fast_token::<10, { Flags::CONNECTION_KEEP_ALIVE }>(raw, b"keep-alive", flags)?
361    {
362        return Ok(Some(out));
363    }
364    if let Some(out) = conn_fast_token::<5, { Flags::CONNECTION_CLOSE }>(raw, b"close", flags)? {
365        return Ok(Some(out));
366    }
367    let Some((found, tail_end, value_start, value_end)) =
368        csv_line(raw, CSV_CLOSE_BIT | CSV_KEEP_ALIVE_BIT)?
369    else {
370        return Ok(None);
371    };
372    flags.mark_seen::<ConnHeader>()?;
373    if (found & CSV_CLOSE_BIT) != 0 {
374        flags.set(Flags::CONNECTION_CLOSE);
375    }
376    if (found & CSV_KEEP_ALIVE_BIT) != 0 {
377        flags.set(Flags::CONNECTION_KEEP_ALIVE);
378    }
379    Ok(Some((tail_end, value_start, value_end)))
380}
381
382fn conn_fast_token<const N: usize, const FLAG: u16>(
383    raw: &[u8],
384    expected: &[u8; N],
385    flags: &mut Flags,
386) -> Result<Option<(usize, usize, usize)>> {
387    let Some((value_start, body_end)) = try_token_line_lit(raw, expected) else {
388        return Ok(None);
389    };
390    flags.mark_seen::<ConnHeader>()?;
391    flags.set(FLAG);
392    Ok(Some((body_end, value_start, body_end)))
393}
394
395fn clen_fast(
396    raw: &[u8],
397    scan: &mut codec::HeaderScan,
398    flags: &mut Flags,
399) -> Result<Option<(usize, usize, usize)>> {
400    let value_start = if raw.first() == Some(&b' ') { 1 } else { 0 };
401    if raw.len() <= value_start + 2 {
402        return Ok(None);
403    }
404    let mut acc: usize = 0;
405    let mut idx = value_start;
406    while idx < raw.len() {
407        let b = raw[idx];
408        if !b.is_ascii_digit() {
409            break;
410        }
411        let Some(next) = acc
412            .checked_mul(10)
413            .and_then(|m| m.checked_add((b - b'0') as usize))
414        else {
415            return Ok(None);
416        };
417        acc = next;
418        idx += 1;
419    }
420    if idx == value_start {
421        return Ok(None);
422    }
423    let value_end = idx;
424    if value_end + 1 >= raw.len() {
425        return Ok(None);
426    }
427    if raw[value_end] != b'\r' || raw[value_end + 1] != b'\n' {
428        return Ok(None);
429    }
430    flags.mark_seen::<ContentLengthHeader>()?;
431    if let Some(existing) = scan.content_length
432        && existing != acc
433    {
434        return Err(Error::BadRequest(
435            "Multiple Content-Length headers are not allowed".into(),
436        ));
437    }
438    scan.content_length = Some(acc);
439    Ok(Some((value_end, value_start, value_end)))
440}
441
442pub fn clen_line(
443    scan: &mut codec::HeaderScan,
444    flags: &mut Flags,
445    raw: &[u8],
446) -> Result<Option<(usize, usize, usize)>> {
447    if let Some(out) = clen_fast(raw, scan, flags)? {
448        return Ok(Some(out));
449    }
450    let Some((len, tail_end, value_start, value_end)) = parse_clen(raw)? else {
451        return Ok(None);
452    };
453    flags.mark_seen::<ContentLengthHeader>()?;
454    if let Some(existing) = scan.content_length
455        && existing != len
456    {
457        return Err(Error::BadRequest(
458            "Multiple Content-Length headers are not allowed".into(),
459        ));
460    }
461    scan.content_length = Some(len);
462    Ok(Some((tail_end, value_start, value_end)))
463}
464
465pub fn te_line(
466    scan: &mut codec::HeaderScan,
467    flags: &mut Flags,
468    raw: &[u8],
469) -> Result<Option<(usize, usize, usize)>> {
470    if let Some(out) = te_fast_chunked(raw, scan, flags)? {
471        return Ok(Some(out));
472    }
473    let Some((tail_end, value_start, value_end)) = trim_line(raw)? else {
474        return Ok(None);
475    };
476    flags.mark_seen::<TransferEncodingHeader>()?;
477    scan.has_transfer_encoding = true;
478    if te_request_chunked_final(&raw[value_start..value_end])? {
479        scan.is_chunked_transfer = true;
480    }
481    Ok(Some((tail_end, value_start, value_end)))
482}
483
484fn te_invalid_transfer_encoding() -> Error {
485    Error::BadRequest("Invalid Transfer-Encoding".into())
486}
487
488fn te_invalid_ordering() -> Error {
489    Error::BadRequest("Invalid Transfer-Encoding ordering".into())
490}
491
492fn te_request_chunked_final(value: &[u8]) -> Result<bool> {
493    let mut saw_chunked = false;
494    let mut last_is_chunked = false;
495    let mut start = 0usize;
496    let mut idx = 0usize;
497    let len = value.len();
498    loop {
499        if idx == len || value[idx] == b',' {
500            let mut lo = start;
501            let mut hi = idx;
502            while lo < hi && is_ascii_ws(value[lo]) {
503                lo += 1;
504            }
505            while hi > lo && is_ascii_ws(value[hi - 1]) {
506                hi -= 1;
507            }
508            if hi <= lo {
509                return Err(te_invalid_transfer_encoding());
510            }
511            let token = &value[lo..hi];
512            if !token.is_ascii() {
513                return Err(te_invalid_transfer_encoding());
514            }
515            let is_chunked = token_eq_ci(token, b"chunked");
516            if saw_chunked && (!last_is_chunked || !is_chunked) {
517                return Err(te_invalid_ordering());
518            }
519            if is_chunked {
520                saw_chunked = true;
521            }
522            last_is_chunked = is_chunked;
523            if idx == len {
524                break;
525            }
526            start = idx + 1;
527        }
528        idx += 1;
529    }
530    Ok(saw_chunked && last_is_chunked)
531}
532
533fn te_fast_chunked(
534    raw: &[u8],
535    scan: &mut codec::HeaderScan,
536    flags: &mut Flags,
537) -> Result<Option<(usize, usize, usize)>> {
538    let Some((value_start, body_end)) = try_token_line_lit(raw, b"chunked") else {
539        return Ok(None);
540    };
541    flags.mark_seen::<TransferEncodingHeader>()?;
542    scan.has_transfer_encoding = true;
543    scan.is_chunked_transfer = true;
544    Ok(Some((body_end, value_start, body_end)))
545}
546
547pub(super) fn ae_line(
548    scan: &mut codec::HeaderScan,
549    _flags: &mut Flags,
550    raw: &[u8],
551) -> Result<Option<(usize, usize, usize)>> {
552    let Some((found, tail_end, value_start, value_end)) = ae_csv_line(raw)? else {
553        return Ok(None);
554    };
555    if found {
556        scan.accept_encoding_gzip = true;
557    }
558    Ok(Some((tail_end, value_start, value_end)))
559}
560
561fn ae_csv_line(raw: &[u8]) -> Result<Option<(bool, usize, usize, usize)>> {
562    walk_csv_line(raw, false, |found, token| {
563        found || token_eq_ci(ae_token_main(token), b"gzip")
564    })
565}
566
567fn ae_token_main(token: &[u8]) -> &[u8] {
568    let mut end = 0usize;
569    while end < token.len() {
570        let b = token[end];
571        if b == b';' || is_ascii_ws(b) {
572            break;
573        }
574        end += 1;
575    }
576    &token[..end]
577}
578
579fn invalid_content_length() -> Error {
580    Error::BadRequest("Invalid Content-Length".into())
581}
582
583fn invalid_header_value() -> Error {
584    Error::BadRequest("Invalid header value".into())
585}
586
587fn scan_content_length_value(raw: &[u8]) -> Result<Option<(usize, usize, usize)>> {
588    let mut idx = 0usize;
589    while idx < raw.len() && is_ascii_ws(raw[idx]) {
590        idx += 1;
591    }
592    let start = idx;
593    if start == raw.len() {
594        return Ok(None);
595    }
596    while idx < raw.len() && raw[idx].is_ascii_digit() {
597        idx += 1;
598    }
599    if idx == start {
600        return Err(invalid_content_length());
601    }
602    let end = idx;
603    let value = Ascii::parse_usize(&raw[start..end]).ok_or_else(invalid_content_length)?;
604    Ok(Some((value, start, end)))
605}
606
607struct CsvScan<'a> {
608    raw: &'a [u8],
609    pos: usize,
610}
611
612impl<'a> CsvScan<'a> {
613    fn new(raw: &'a [u8]) -> Self {
614        Self { raw, pos: 0 }
615    }
616}
617
618impl<'a> Iterator for CsvScan<'a> {
619    type Item = (&'a [u8], usize);
620
621    fn next(&mut self) -> Option<Self::Item> {
622        if self.pos > self.raw.len() {
623            return None;
624        }
625        while self.pos < self.raw.len() && is_ascii_ws(self.raw[self.pos]) {
626            self.pos += 1;
627        }
628        let start = self.pos;
629        while self.pos < self.raw.len() && self.raw[self.pos] != b',' {
630            self.pos += 1;
631        }
632        let end = self.pos;
633        let mut stop = end;
634        while stop > start && is_ascii_ws(self.raw[stop - 1]) {
635            stop -= 1;
636        }
637        self.pos = end.saturating_add(1);
638        if start > end {
639            return None;
640        }
641        Some((&self.raw[start..stop], stop))
642    }
643}
644
645fn parse_csv(raw: &[u8], want: u8) -> u8 {
646    let mut found = 0u8;
647    for (token, _) in CsvScan::new(raw) {
648        if found == want {
649            break;
650        }
651        found |= match_csv_token(token, want);
652    }
653    found
654}
655
656fn trim_len(raw: &[u8]) -> usize {
657    let mut start = 0usize;
658    while start < raw.len() && is_ascii_ws(raw[start]) {
659        start += 1;
660    }
661    let mut end = raw.len();
662    while end > start && is_ascii_ws(raw[end - 1]) {
663        end -= 1;
664    }
665    end - start
666}
667
668fn trim_line(raw: &[u8]) -> Result<Option<(usize, usize, usize)>> {
669    let mut idx = 0usize;
670    while idx < raw.len() && is_ascii_ws(raw[idx]) {
671        idx += 1;
672    }
673    let value_start = idx;
674    let mut value_end = value_start;
675    loop {
676        if idx >= raw.len() {
677            return Ok(None);
678        }
679        let b = raw[idx];
680        if b == b'\r' {
681            if idx + 1 >= raw.len() {
682                return Ok(None);
683            }
684            if raw[idx + 1] != b'\n' {
685                return Err(invalid_header_value());
686            }
687            return Ok(Some((idx, value_start, value_end)));
688        }
689        if b == b'\n' {
690            return Err(invalid_header_value());
691        }
692        if !is_ascii_ws(b) {
693            value_end = idx + 1;
694        }
695        idx += 1;
696    }
697}
698
699fn parse_content_length_line(raw: &[u8]) -> Result<(usize, usize)> {
700    let (value, start, end) = scan_content_length_value(raw)?.ok_or_else(invalid_content_length)?;
701    let mut idx = end;
702    while idx < raw.len() && is_ascii_ws(raw[idx]) {
703        idx += 1;
704    }
705    if idx != raw.len() {
706        return Err(invalid_content_length());
707    }
708    Ok((value, end - start))
709}
710
711fn parse_clen(raw: &[u8]) -> Result<Option<(usize, usize, usize, usize)>> {
712    let Some((value, value_start, value_end)) = scan_content_length_value(raw)? else {
713        return Ok(None);
714    };
715    let mut idx = value_end;
716    while idx < raw.len() && is_ascii_ws(raw[idx]) {
717        idx += 1;
718    }
719    if idx >= raw.len() {
720        return Ok(None);
721    }
722    if raw[idx] != b'\r' {
723        return Err(invalid_content_length());
724    }
725    if idx + 1 >= raw.len() {
726        return Ok(None);
727    }
728    if raw[idx + 1] != b'\n' {
729        return Err(invalid_content_length());
730    }
731    Ok(Some((value, idx, value_start, value_end)))
732}
733
734fn trim_left(raw: &[u8]) -> usize {
735    let mut start = 0usize;
736    while start < raw.len() && is_ascii_ws(raw[start]) {
737        start += 1;
738    }
739    start
740}
741
742fn token_eq_ci<const N: usize>(token: &[u8], expected: &[u8; N]) -> bool {
743    token.len() == N && Word::swar_eq_ci::<N>(token, expected)
744}
745
746fn match_csv_token(token: &[u8], want: u8) -> u8 {
747    if (want & CSV_CLOSE_BIT) != 0 && token_eq_ci(token, b"close") {
748        return CSV_CLOSE_BIT;
749    }
750    if (want & CSV_KEEP_ALIVE_BIT) != 0 && token_eq_ci(token, b"keep-alive") {
751        return CSV_KEEP_ALIVE_BIT;
752    }
753    if (want & CSV_CHUNKED_BIT) != 0 && token_eq_ci(token, b"chunked") {
754        return CSV_CHUNKED_BIT;
755    }
756    if (want & CSV_CONTINUE_BIT) != 0 && token_eq_ci(token, b"100-continue") {
757        return CSV_CONTINUE_BIT;
758    }
759    0
760}
761
762fn parse_csv_line(raw: &[u8], want: u8) -> (u8, usize) {
763    let mut found = 0u8;
764    let mut value_len = 0usize;
765    for (token, stop) in CsvScan::new(raw) {
766        if found == want {
767            break;
768        }
769        if !token.is_empty() {
770            value_len = stop;
771            found |= match_csv_token(token, want);
772        }
773    }
774    (found, value_len.saturating_sub(trim_left(raw)))
775}
776
777fn csv_line(raw: &[u8], want: u8) -> Result<Option<(u8, usize, usize, usize)>> {
778    walk_csv_line(raw, 0u8, |found, token| {
779        found | match_csv_token(token, want)
780    })
781}
782
783fn walk_csv_line<A: Copy, F: FnMut(A, &[u8]) -> A>(
784    raw: &[u8],
785    init: A,
786    mut fold: F,
787) -> Result<Option<(A, usize, usize, usize)>> {
788    let mut acc = init;
789    let mut idx = 0usize;
790    let mut first_non_ws: Option<usize> = None;
791    let mut last_non_ws = 0usize;
792    loop {
793        let Some(rel) = memchr::memchr3(b',', b'\r', b'\n', &raw[idx..]) else {
794            return Ok(None);
795        };
796        let end = idx + rel;
797        if raw[end] == b'\n' {
798            return Err(invalid_header_value());
799        }
800        let mut t_lo = idx;
801        while t_lo < end && is_ascii_ws(raw[t_lo]) {
802            t_lo += 1;
803        }
804        let mut t_hi = end;
805        while t_hi > t_lo && is_ascii_ws(raw[t_hi - 1]) {
806            t_hi -= 1;
807        }
808        if t_hi > t_lo {
809            if first_non_ws.is_none() {
810                first_non_ws = Some(t_lo);
811            }
812            last_non_ws = t_hi;
813            acc = fold(acc, &raw[t_lo..t_hi]);
814        }
815        if raw[end] == b'\r' {
816            if end + 1 >= raw.len() {
817                return Ok(None);
818            }
819            if raw[end + 1] != b'\n' {
820                return Err(invalid_header_value());
821            }
822            let value_start = first_non_ws.unwrap_or(0);
823            let value_end = if first_non_ws.is_some() {
824                last_non_ws
825            } else {
826                value_start
827            };
828            return Ok(Some((acc, end, value_start, value_end)));
829        }
830        idx = end + 1;
831    }
832}
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837
838    #[test]
839    fn partial_known_header_lines_do_not_commit_seen_state() {
840        let mut scan = codec::HeaderScan {
841            content_length: None,
842            has_transfer_encoding: false,
843            is_chunked_transfer: false,
844            has_expect: false,
845            expect_continue: false,
846            duplicate_content_length: false,
847            accept_encoding_gzip: false,
848        };
849        let mut flags = Flags::default();
850
851        assert!(matches!(
852            host_line(&mut scan, &mut flags, b" localhost"),
853            Ok(None)
854        ));
855        assert!(!flags.has(Flags::SEEN_HOST));
856        assert!(!flags.has(Flags::HAS_HOST));
857
858        assert!(matches!(
859            conn_line(&mut scan, &mut flags, b" keep-alive"),
860            Ok(None)
861        ));
862        assert!(!flags.has(Flags::SEEN_CONNECTION));
863        assert!(!flags.has(Flags::CONNECTION_KEEP_ALIVE));
864        assert!(!flags.has(Flags::CONNECTION_CLOSE));
865
866        assert!(matches!(
867            clen_line(&mut scan, &mut flags, b" 123"),
868            Ok(None)
869        ));
870        assert!(!flags.has(Flags::SEEN_CONTENT_LENGTH));
871        assert!(scan.content_length.is_none());
872
873        assert!(matches!(
874            te_line(&mut scan, &mut flags, b" chunked"),
875            Ok(None)
876        ));
877        assert!(!flags.has(Flags::SEEN_TRANSFER_ENCODING));
878        assert!(!scan.has_transfer_encoding);
879        assert!(!scan.is_chunked_transfer);
880
881        assert!(matches!(
882            expect_line(&mut scan, &mut flags, b" 100-continue"),
883            Ok(None)
884        ));
885        assert!(!flags.has(Flags::SEEN_EXPECT));
886        assert!(!scan.has_expect);
887        assert!(!scan.expect_continue);
888    }
889}