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 Some(rel) = memchr::memchr2(b'\r', b'\n', &raw[value_start..]) else {
677 return Ok(None);
678 };
679 let cr = value_start + rel;
680 if raw[cr] == b'\n' {
681 return Err(invalid_header_value());
682 }
683 if cr + 1 >= raw.len() {
684 return Ok(None);
685 }
686 if raw[cr + 1] != b'\n' {
687 return Err(invalid_header_value());
688 }
689 let mut value_end = cr;
690 while value_end > value_start && is_ascii_ws(raw[value_end - 1]) {
691 value_end -= 1;
692 }
693 Ok(Some((cr, value_start, value_end)))
694}
695
696fn parse_content_length_line(raw: &[u8]) -> Result<(usize, usize)> {
697 let (value, start, end) = scan_content_length_value(raw)?.ok_or_else(invalid_content_length)?;
698 let mut idx = end;
699 while idx < raw.len() && is_ascii_ws(raw[idx]) {
700 idx += 1;
701 }
702 if idx != raw.len() {
703 return Err(invalid_content_length());
704 }
705 Ok((value, end - start))
706}
707
708fn parse_clen(raw: &[u8]) -> Result<Option<(usize, usize, usize, usize)>> {
709 let Some((value, value_start, value_end)) = scan_content_length_value(raw)? else {
710 return Ok(None);
711 };
712 let mut idx = value_end;
713 while idx < raw.len() && is_ascii_ws(raw[idx]) {
714 idx += 1;
715 }
716 if idx >= raw.len() {
717 return Ok(None);
718 }
719 if raw[idx] != b'\r' {
720 return Err(invalid_content_length());
721 }
722 if idx + 1 >= raw.len() {
723 return Ok(None);
724 }
725 if raw[idx + 1] != b'\n' {
726 return Err(invalid_content_length());
727 }
728 Ok(Some((value, idx, value_start, value_end)))
729}
730
731fn trim_left(raw: &[u8]) -> usize {
732 let mut start = 0usize;
733 while start < raw.len() && is_ascii_ws(raw[start]) {
734 start += 1;
735 }
736 start
737}
738
739fn token_eq_ci<const N: usize>(token: &[u8], expected: &[u8; N]) -> bool {
740 token.len() == N && Word::swar_eq_ci::<N>(token, expected)
741}
742
743fn match_csv_token(token: &[u8], want: u8) -> u8 {
744 if (want & CSV_CLOSE_BIT) != 0 && token_eq_ci(token, b"close") {
745 return CSV_CLOSE_BIT;
746 }
747 if (want & CSV_KEEP_ALIVE_BIT) != 0 && token_eq_ci(token, b"keep-alive") {
748 return CSV_KEEP_ALIVE_BIT;
749 }
750 if (want & CSV_CHUNKED_BIT) != 0 && token_eq_ci(token, b"chunked") {
751 return CSV_CHUNKED_BIT;
752 }
753 if (want & CSV_CONTINUE_BIT) != 0 && token_eq_ci(token, b"100-continue") {
754 return CSV_CONTINUE_BIT;
755 }
756 0
757}
758
759fn parse_csv_line(raw: &[u8], want: u8) -> (u8, usize) {
760 let mut found = 0u8;
761 let mut value_len = 0usize;
762 for (token, stop) in CsvScan::new(raw) {
763 if found == want {
764 break;
765 }
766 if !token.is_empty() {
767 value_len = stop;
768 found |= match_csv_token(token, want);
769 }
770 }
771 (found, value_len.saturating_sub(trim_left(raw)))
772}
773
774fn csv_line(raw: &[u8], want: u8) -> Result<Option<(u8, usize, usize, usize)>> {
775 walk_csv_line(raw, 0u8, |found, token| {
776 found | match_csv_token(token, want)
777 })
778}
779
780fn walk_csv_line<A: Copy, F: FnMut(A, &[u8]) -> A>(
781 raw: &[u8],
782 init: A,
783 mut fold: F,
784) -> Result<Option<(A, usize, usize, usize)>> {
785 let mut acc = init;
786 let mut idx = 0usize;
787 let mut first_non_ws: Option<usize> = None;
788 let mut last_non_ws = 0usize;
789 loop {
790 let Some(rel) = memchr::memchr3(b',', b'\r', b'\n', &raw[idx..]) else {
791 return Ok(None);
792 };
793 let end = idx + rel;
794 if raw[end] == b'\n' {
795 return Err(invalid_header_value());
796 }
797 let mut t_lo = idx;
798 while t_lo < end && is_ascii_ws(raw[t_lo]) {
799 t_lo += 1;
800 }
801 let mut t_hi = end;
802 while t_hi > t_lo && is_ascii_ws(raw[t_hi - 1]) {
803 t_hi -= 1;
804 }
805 if t_hi > t_lo {
806 if first_non_ws.is_none() {
807 first_non_ws = Some(t_lo);
808 }
809 last_non_ws = t_hi;
810 acc = fold(acc, &raw[t_lo..t_hi]);
811 }
812 if raw[end] == b'\r' {
813 if end + 1 >= raw.len() {
814 return Ok(None);
815 }
816 if raw[end + 1] != b'\n' {
817 return Err(invalid_header_value());
818 }
819 let value_start = first_non_ws.unwrap_or(0);
820 let value_end = if first_non_ws.is_some() {
821 last_non_ws
822 } else {
823 value_start
824 };
825 return Ok(Some((acc, end, value_start, value_end)));
826 }
827 idx = end + 1;
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[test]
836 fn partial_known_header_lines_do_not_commit_seen_state() {
837 let mut scan = codec::HeaderScan {
838 content_length: None,
839 has_transfer_encoding: false,
840 is_chunked_transfer: false,
841 has_expect: false,
842 expect_continue: false,
843 duplicate_content_length: false,
844 accept_encoding_gzip: false,
845 };
846 let mut flags = Flags::default();
847
848 assert!(matches!(
849 host_line(&mut scan, &mut flags, b" localhost"),
850 Ok(None)
851 ));
852 assert!(!flags.has(Flags::SEEN_HOST));
853 assert!(!flags.has(Flags::HAS_HOST));
854
855 assert!(matches!(
856 conn_line(&mut scan, &mut flags, b" keep-alive"),
857 Ok(None)
858 ));
859 assert!(!flags.has(Flags::SEEN_CONNECTION));
860 assert!(!flags.has(Flags::CONNECTION_KEEP_ALIVE));
861 assert!(!flags.has(Flags::CONNECTION_CLOSE));
862
863 assert!(matches!(
864 clen_line(&mut scan, &mut flags, b" 123"),
865 Ok(None)
866 ));
867 assert!(!flags.has(Flags::SEEN_CONTENT_LENGTH));
868 assert!(scan.content_length.is_none());
869
870 assert!(matches!(
871 te_line(&mut scan, &mut flags, b" chunked"),
872 Ok(None)
873 ));
874 assert!(!flags.has(Flags::SEEN_TRANSFER_ENCODING));
875 assert!(!scan.has_transfer_encoding);
876 assert!(!scan.is_chunked_transfer);
877
878 assert!(matches!(
879 expect_line(&mut scan, &mut flags, b" 100-continue"),
880 Ok(None)
881 ));
882 assert!(!flags.has(Flags::SEEN_EXPECT));
883 assert!(!scan.has_expect);
884 assert!(!scan.expect_continue);
885 }
886}