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