1use crate::{
25 class,
26 error::{ErrorKind, Result, err},
27 limits::Limits,
28 scan::control_kind,
29 text::{self, comment_end, parse_quoted, trim_ows},
30};
31
32#[derive(Clone, Copy, Default)]
34struct Budget {
35 members: usize,
36 empties: usize,
37}
38
39#[inline]
42fn is_simple(value: &[u8]) -> bool {
43 !value.iter().any(|&b| matches!(b, b'"' | b'(' | b')'))
44}
45
46#[inline]
49fn segment<'a>(
50 value: &'a [u8],
51 start: usize,
52 end: usize,
53 limits: &Limits,
54 budget: &mut Budget,
55 f: &mut impl FnMut(&'a [u8]),
56) -> Result<()> {
57 let raw = value.get(start..end).unwrap_or(&[]);
58 let element = trim_ows(raw);
59 if element.is_empty() {
60 if !(end == value.len() && start == 0) {
62 budget.empties += 1;
63 if budget.empties > usize::from(limits.empty_elements) {
64 return err(ErrorKind::TooManyEmptyElements, end);
65 }
66 }
67 } else {
68 budget.members += 1;
69 if budget.members > usize::from(limits.list_members) {
70 return err(ErrorKind::TooManyMembers, start + text::skip_ows(raw));
71 }
72 f(element);
73 }
74 Ok(())
75}
76
77fn walk<'a>(
81 value: &'a [u8],
82 limits: &Limits,
83 budget: &mut Budget,
84 mut f: impl FnMut(&'a [u8]),
85) -> Result<bool> {
86 let mut simple = true;
87 let mut start = 0usize;
88 let mut i = 0usize;
89 loop {
90 let rest = value.get(i..).unwrap_or(&[]);
91 i += rest
92 .iter()
93 .position(|&b| !class::is_list_plain(b))
94 .unwrap_or(rest.len());
95 match value.get(i) {
96 None => {
97 segment(value, start, i, limits, budget, &mut f)?;
98 return Ok(simple);
99 }
100 Some(b',') => {
101 segment(value, start, i, limits, budget, &mut f)?;
102 i += 1;
103 start = i;
104 }
105 Some(b'"') => {
106 simple = false;
107 let rest = value.get(i..).unwrap_or(&[]);
108 let (_, used) = parse_quoted(rest).map_err(|e| e.shifted(i))?;
109 i += used;
110 }
111 Some(b'(') => {
112 simple = false;
113 let (end, _) = comment_end(value, i, limits)?;
114 i = end;
115 }
116 Some(b')') => return err(ErrorKind::UnbalancedComment, i),
117 Some(&b) => return err(control_kind(b), i),
119 }
120 }
121}
122
123pub fn list(value: &[u8]) -> Result<List<'_>> {
137 parse(value, &Limits::DEFAULT)
138}
139
140pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<List<'a>> {
167 let mut budget = Budget::default();
168 let simple = walk(value, limits, &mut budget, |_| {})?;
169 Ok(List {
170 value,
171 count: u32::try_from(budget.members).unwrap_or(u32::MAX),
173 simple,
174 })
175}
176
177pub fn for_each<'a>(value: &'a [u8], limits: &Limits, f: impl FnMut(&'a [u8])) -> Result<usize> {
197 let mut budget = Budget::default();
198 let _simple = walk(value, limits, &mut budget, f)?;
199 Ok(budget.members)
200}
201
202pub fn combined<'a, I>(lines: I, limits: &Limits) -> Result<CombinedList<'a, I::IntoIter>>
223where
224 I: IntoIterator<Item = &'a [u8]>,
225 I::IntoIter: Clone,
226{
227 let lines = lines.into_iter();
228 let mut budget = Budget::default();
229 for (n, line) in lines.clone().enumerate() {
230 let _simple = walk(line, limits, &mut budget, |_| {}).map_err(|e| e.on_line(n))?;
231 }
232 Ok(CombinedList {
233 lines,
234 count: budget.members,
235 _value: core::marker::PhantomData,
236 })
237}
238
239pub fn contains_token<'a, I>(lines: I, token: &[u8]) -> bool
253where
254 I: IntoIterator<Item = &'a [u8]>,
255{
256 lines.into_iter().any(|line| {
257 parse(line, &Limits::DEFAULT).is_ok_and(|l| l.iter().any(|e| e.eq_ignore_ascii_case(token)))
258 })
259}
260
261#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
272pub struct List<'a> {
273 value: &'a [u8],
274 count: u32,
275 simple: bool,
276}
277
278impl<'a> List<'a> {
279 #[must_use]
292 pub fn iter(&self) -> Elements<'a> {
293 Elements {
294 rest: self.value,
295 remaining: self.count as usize,
296 simple: self.simple,
297 }
298 }
299
300 #[must_use]
309 pub const fn len(&self) -> usize {
310 self.count as usize
311 }
312
313 #[must_use]
323 pub const fn is_empty(&self) -> bool {
324 self.count == 0
325 }
326}
327
328impl<'a> IntoIterator for List<'a> {
329 type Item = &'a [u8];
330 type IntoIter = Elements<'a>;
331
332 fn into_iter(self) -> Elements<'a> {
333 self.iter()
334 }
335}
336
337fn top_level_comma(s: &[u8]) -> Option<usize> {
340 let mut i = 0usize;
341 let mut depth = 0u32;
342 let mut quoted = false;
343 while let Some(&b) = s.get(i) {
344 match b {
345 b'\\' if quoted || depth > 0 => i += 1,
346 b'"' if depth == 0 => quoted = !quoted,
347 b'(' if !quoted => depth += 1,
348 b')' if !quoted => depth = depth.saturating_sub(1),
349 b',' if !quoted && depth == 0 => return Some(i),
350 _ => {}
351 }
352 i += 1;
353 }
354 None
355}
356
357#[derive(Clone, Debug)]
369pub struct Elements<'a> {
370 rest: &'a [u8],
371 remaining: usize,
372 simple: bool,
373}
374
375impl<'a> Iterator for Elements<'a> {
376 type Item = &'a [u8];
377
378 fn next(&mut self) -> Option<&'a [u8]> {
379 while self.remaining > 0 && !self.rest.is_empty() {
380 let end = if self.simple {
381 self.rest.iter().position(|&b| b == b',')
382 } else {
383 top_level_comma(self.rest)
384 }
385 .unwrap_or(self.rest.len());
386 let element = trim_ows(self.rest.get(..end).unwrap_or(&[]));
387 self.rest = self.rest.get(end + 1..).unwrap_or(&[]);
388 if !element.is_empty() {
389 self.remaining -= 1;
390 return Some(element);
391 }
392 }
393 None
394 }
395
396 fn size_hint(&self) -> (usize, Option<usize>) {
397 (self.remaining, Some(self.remaining))
398 }
399}
400
401impl ExactSizeIterator for Elements<'_> {}
402
403#[derive(Clone, Debug)]
417pub struct CombinedList<'a, I> {
418 lines: I,
419 count: usize,
420 _value: core::marker::PhantomData<&'a [u8]>,
421}
422
423impl<'a, I: Iterator<Item = &'a [u8]> + Clone> CombinedList<'a, I> {
424 #[must_use]
437 pub fn iter(&self) -> CombinedElements<'a, I> {
438 CombinedElements {
439 lines: self.lines.clone(),
440 current: Elements {
441 rest: &[],
442 remaining: 0,
443 simple: true,
444 },
445 remaining: self.count,
446 }
447 }
448
449 #[must_use]
461 pub const fn len(&self) -> usize {
462 self.count
463 }
464
465 #[must_use]
477 pub const fn is_empty(&self) -> bool {
478 self.count == 0
479 }
480}
481
482#[derive(Clone, Debug)]
497pub struct CombinedElements<'a, I> {
498 lines: I,
499 current: Elements<'a>,
500 remaining: usize,
501}
502
503impl<'a, I: Iterator<Item = &'a [u8]>> Iterator for CombinedElements<'a, I> {
504 type Item = &'a [u8];
505
506 fn next(&mut self) -> Option<&'a [u8]> {
507 while self.remaining > 0 {
508 if let Some(e) = self.current.next() {
509 self.remaining -= 1;
510 return Some(e);
511 }
512 let line = self.lines.next()?;
513 self.current = Elements {
516 rest: line,
517 remaining: usize::MAX,
518 simple: is_simple(line),
519 };
520 }
521 None
522 }
523
524 fn size_hint(&self) -> (usize, Option<usize>) {
525 (self.remaining, Some(self.remaining))
526 }
527}
528
529impl<'a, I: Iterator<Item = &'a [u8]>> ExactSizeIterator for CombinedElements<'a, I> {}
530
531#[cfg(test)]
532mod tests {
533 use std::{vec, vec::Vec};
534
535 use super::*;
536
537 fn all(v: &[u8]) -> Vec<&[u8]> {
538 list(v).unwrap().iter().collect()
539 }
540
541 #[test]
542 fn test_list_edge_cases() {
543 assert!(all(b"").is_empty());
544 assert!(all(b" ").is_empty());
545 assert!(all(b",").is_empty());
546 assert!(all(b", ,").is_empty());
547 assert_eq!(all(b"a,"), [b"a"]);
548 assert_eq!(all(b",a"), [b"a"]);
549 assert_eq!(all(b"a,,b"), [&b"a"[..], b"b"]);
550 assert_eq!(all(b"a, \"b,c\""), [&b"a"[..], b"\"b,c\""]);
551 assert_eq!(all(b"a, (b,c)"), [&b"a"[..], b"(b,c)"]);
552 assert_eq!(all(b"a;q=1 , b"), [&b"a;q=1"[..], b"b"]);
553 assert_eq!(all(b"a=\"x\\\",y\", b"), [&b"a=\"x\\\",y\""[..], b"b"]);
554 assert_eq!(all(b"(a\\)b,c), d"), [&b"(a\\)b,c)"[..], b"d"]);
555 }
556
557 #[test]
558 fn test_list_comma_in_quotes_is_one_element() {
559 assert_eq!(all(b"filename=\"a,b.txt\""), [&b"filename=\"a,b.txt\""[..]]);
560 }
561
562 #[test]
563 fn test_list_empty_element_budget() {
564 let mut sixteen = vec![b'a'];
566 sixteen.extend([b','; 17]);
567 sixteen.push(b'b');
568 assert_eq!(all(&sixteen), [&b"a"[..], b"b"]);
569 let mut seventeen = vec![b'a'];
570 seventeen.extend([b','; 18]);
571 seventeen.push(b'b');
572 let e = list(&seventeen).unwrap_err();
573 assert_eq!(
574 (e.kind(), e.offset()),
575 (ErrorKind::TooManyEmptyElements, 18)
576 );
577 }
578
579 #[test]
580 fn test_list_member_budget() {
581 let mut v = Vec::new();
582 for i in 0..1025 {
583 if i > 0 {
584 v.extend_from_slice(b", ");
585 }
586 v.push(b'a');
587 }
588 assert_eq!(list(&v[..v.len() - 3]).unwrap().len(), 1024);
589 assert_eq!(list(&v).unwrap_err().kind(), ErrorKind::TooManyMembers);
590 let e = parse(b"a, b, c", &Limits::DEFAULT.max_list_members(2)).unwrap_err();
591 assert_eq!(e.offset(), 6);
592 }
593
594 #[test]
595 fn test_list_refuses_bad_bytes_and_structure() {
596 assert_eq!(list(b"a\rb").unwrap_err().kind(), ErrorKind::BareCr);
597 assert_eq!(
598 list(b"a, \"b").unwrap_err().kind(),
599 ErrorKind::UnterminatedQuote
600 );
601 assert_eq!(
602 list(b"a)").unwrap_err().kind(),
603 ErrorKind::UnbalancedComment
604 );
605 assert_eq!(
606 list(b"(a").unwrap_err().kind(),
607 ErrorKind::UnbalancedComment
608 );
609 let e = list(b"ab, \"c\x01\"").unwrap_err();
610 assert_eq!((e.kind(), e.offset()), (ErrorKind::Control, 6));
611 }
612
613 #[test]
614 fn test_combined_lines() {
615 let lines: [&[u8]; 3] = [b"a, b", b"", b"c"];
616 let c = combined(lines, &Limits::DEFAULT).unwrap();
617 assert_eq!(c.len(), 3);
618 assert_eq!(c.iter().collect::<Vec<_>>(), [&b"a"[..], b"b", b"c"]);
619 assert_eq!(c.iter().len(), 3);
620 let bad: [&[u8]; 2] = [b"a", b"\"open"];
621 let e = combined(bad, &Limits::DEFAULT).unwrap_err();
622 assert_eq!((e.kind(), e.line()), (ErrorKind::UnterminatedQuote, 1));
623 let empty: [&[u8]; 0] = [];
624 assert!(combined(empty, &Limits::DEFAULT).unwrap().is_empty());
625 }
626
627 #[test]
628 fn test_combined_budgets_count_across_lines() {
629 let l = Limits::DEFAULT.max_list_members(2);
630 let lines: [&[u8]; 2] = [b"a", b"b, c"];
631 let e = combined(lines, &l).unwrap_err();
632 assert_eq!(
633 (e.kind(), e.line(), e.offset()),
634 (ErrorKind::TooManyMembers, 1, 3)
635 );
636 }
637
638 #[test]
639 fn test_combined_equals_joined_at_every_comma() {
640 let value = b"a, \"b,c\", (d,e), f;g=\"h\", i";
641 let whole = all(value);
642 let mut commas = Vec::new();
643 let mut from = 0;
644 while let Some(i) = top_level_comma(&value[from..]) {
645 commas.push(from + i);
646 from += i + 1;
647 }
648 assert_eq!(commas.len(), 4);
649 for &split in &commas {
650 let lines: [&[u8]; 2] = [&value[..split], &value[split + 1..]];
651 let c = combined(lines, &Limits::DEFAULT).unwrap();
652 assert_eq!(c.iter().collect::<Vec<_>>(), whole, "split at {split}");
653 }
654 }
655
656 #[test]
657 fn test_contains_token_fails_closed() {
658 assert!(contains_token(
659 [&b"close"[..], b"keep-alive, Upgrade"],
660 b"upgrade"
661 ));
662 assert!(!contains_token([&b"upgrade\r"[..]], b"upgrade"));
663 let none: [&[u8]; 0] = [];
664 assert!(!contains_token(none, b"x"));
665 }
666
667 #[test]
668 fn test_for_each_sees_every_element() {
669 let mut seen = vec![];
670 let n = for_each(b"a, b ,c", &Limits::DEFAULT, |e| seen.push(e)).unwrap();
671 assert_eq!((n, seen), (3, vec![&b"a"[..], b"b", b"c"]));
672 }
673}