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}