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)]
263pub struct List<'a> {
264 value: &'a [u8],
265 count: u32,
266 simple: bool,
267}
268
269impl<'a> List<'a> {
270 #[must_use]
272 pub fn iter(&self) -> Elements<'a> {
273 Elements {
274 rest: self.value,
275 remaining: self.count as usize,
276 simple: self.simple,
277 }
278 }
279
280 #[must_use]
282 pub const fn len(&self) -> usize {
283 self.count as usize
284 }
285
286 #[must_use]
288 pub const fn is_empty(&self) -> bool {
289 self.count == 0
290 }
291}
292
293impl<'a> IntoIterator for List<'a> {
294 type Item = &'a [u8];
295 type IntoIter = Elements<'a>;
296
297 fn into_iter(self) -> Elements<'a> {
298 self.iter()
299 }
300}
301
302fn top_level_comma(s: &[u8]) -> Option<usize> {
305 let mut i = 0usize;
306 let mut depth = 0u32;
307 let mut quoted = false;
308 while let Some(&b) = s.get(i) {
309 match b {
310 b'\\' if quoted || depth > 0 => i += 1,
311 b'"' if depth == 0 => quoted = !quoted,
312 b'(' if !quoted => depth += 1,
313 b')' if !quoted => depth = depth.saturating_sub(1),
314 b',' if !quoted && depth == 0 => return Some(i),
315 _ => {}
316 }
317 i += 1;
318 }
319 None
320}
321
322#[derive(Clone, Debug)]
324pub struct Elements<'a> {
325 rest: &'a [u8],
326 remaining: usize,
327 simple: bool,
328}
329
330impl<'a> Iterator for Elements<'a> {
331 type Item = &'a [u8];
332
333 fn next(&mut self) -> Option<&'a [u8]> {
334 while self.remaining > 0 && !self.rest.is_empty() {
335 let end = if self.simple {
336 self.rest.iter().position(|&b| b == b',')
337 } else {
338 top_level_comma(self.rest)
339 }
340 .unwrap_or(self.rest.len());
341 let element = trim_ows(self.rest.get(..end).unwrap_or(&[]));
342 self.rest = self.rest.get(end + 1..).unwrap_or(&[]);
343 if !element.is_empty() {
344 self.remaining -= 1;
345 return Some(element);
346 }
347 }
348 None
349 }
350
351 fn size_hint(&self) -> (usize, Option<usize>) {
352 (self.remaining, Some(self.remaining))
353 }
354}
355
356impl ExactSizeIterator for Elements<'_> {}
357
358#[derive(Clone, Debug)]
360pub struct CombinedList<'a, I> {
361 lines: I,
362 count: usize,
363 _value: core::marker::PhantomData<&'a [u8]>,
364}
365
366impl<'a, I: Iterator<Item = &'a [u8]> + Clone> CombinedList<'a, I> {
367 #[must_use]
369 pub fn iter(&self) -> CombinedElements<'a, I> {
370 CombinedElements {
371 lines: self.lines.clone(),
372 current: Elements {
373 rest: &[],
374 remaining: 0,
375 simple: true,
376 },
377 remaining: self.count,
378 }
379 }
380
381 #[must_use]
383 pub const fn len(&self) -> usize {
384 self.count
385 }
386
387 #[must_use]
389 pub const fn is_empty(&self) -> bool {
390 self.count == 0
391 }
392}
393
394#[derive(Clone, Debug)]
396pub struct CombinedElements<'a, I> {
397 lines: I,
398 current: Elements<'a>,
399 remaining: usize,
400}
401
402impl<'a, I: Iterator<Item = &'a [u8]>> Iterator for CombinedElements<'a, I> {
403 type Item = &'a [u8];
404
405 fn next(&mut self) -> Option<&'a [u8]> {
406 while self.remaining > 0 {
407 if let Some(e) = self.current.next() {
408 self.remaining -= 1;
409 return Some(e);
410 }
411 let line = self.lines.next()?;
412 self.current = Elements {
415 rest: line,
416 remaining: usize::MAX,
417 simple: is_simple(line),
418 };
419 }
420 None
421 }
422
423 fn size_hint(&self) -> (usize, Option<usize>) {
424 (self.remaining, Some(self.remaining))
425 }
426}
427
428impl<'a, I: Iterator<Item = &'a [u8]>> ExactSizeIterator for CombinedElements<'a, I> {}
429
430#[cfg(test)]
431mod tests {
432 use std::{vec, vec::Vec};
433
434 use super::*;
435
436 fn all(v: &[u8]) -> Vec<&[u8]> {
437 list(v).unwrap().iter().collect()
438 }
439
440 #[test]
441 fn test_list_edge_cases() {
442 assert!(all(b"").is_empty());
443 assert!(all(b" ").is_empty());
444 assert!(all(b",").is_empty());
445 assert!(all(b", ,").is_empty());
446 assert_eq!(all(b"a,"), [b"a"]);
447 assert_eq!(all(b",a"), [b"a"]);
448 assert_eq!(all(b"a,,b"), [&b"a"[..], b"b"]);
449 assert_eq!(all(b"a, \"b,c\""), [&b"a"[..], b"\"b,c\""]);
450 assert_eq!(all(b"a, (b,c)"), [&b"a"[..], b"(b,c)"]);
451 assert_eq!(all(b"a;q=1 , b"), [&b"a;q=1"[..], b"b"]);
452 assert_eq!(all(b"a=\"x\\\",y\", b"), [&b"a=\"x\\\",y\""[..], b"b"]);
453 assert_eq!(all(b"(a\\)b,c), d"), [&b"(a\\)b,c)"[..], b"d"]);
454 }
455
456 #[test]
457 fn test_list_comma_in_quotes_is_one_element() {
458 assert_eq!(all(b"filename=\"a,b.txt\""), [&b"filename=\"a,b.txt\""[..]]);
459 }
460
461 #[test]
462 fn test_list_empty_element_budget() {
463 let mut sixteen = vec![b'a'];
465 sixteen.extend([b','; 17]);
466 sixteen.push(b'b');
467 assert_eq!(all(&sixteen), [&b"a"[..], b"b"]);
468 let mut seventeen = vec![b'a'];
469 seventeen.extend([b','; 18]);
470 seventeen.push(b'b');
471 let e = list(&seventeen).unwrap_err();
472 assert_eq!(
473 (e.kind(), e.offset()),
474 (ErrorKind::TooManyEmptyElements, 18)
475 );
476 }
477
478 #[test]
479 fn test_list_member_budget() {
480 let mut v = Vec::new();
481 for i in 0..1025 {
482 if i > 0 {
483 v.extend_from_slice(b", ");
484 }
485 v.push(b'a');
486 }
487 assert_eq!(list(&v[..v.len() - 3]).unwrap().len(), 1024);
488 assert_eq!(list(&v).unwrap_err().kind(), ErrorKind::TooManyMembers);
489 let e = parse(b"a, b, c", &Limits::DEFAULT.max_list_members(2)).unwrap_err();
490 assert_eq!(e.offset(), 6);
491 }
492
493 #[test]
494 fn test_list_refuses_bad_bytes_and_structure() {
495 assert_eq!(list(b"a\rb").unwrap_err().kind(), ErrorKind::BareCr);
496 assert_eq!(
497 list(b"a, \"b").unwrap_err().kind(),
498 ErrorKind::UnterminatedQuote
499 );
500 assert_eq!(
501 list(b"a)").unwrap_err().kind(),
502 ErrorKind::UnbalancedComment
503 );
504 assert_eq!(
505 list(b"(a").unwrap_err().kind(),
506 ErrorKind::UnbalancedComment
507 );
508 let e = list(b"ab, \"c\x01\"").unwrap_err();
509 assert_eq!((e.kind(), e.offset()), (ErrorKind::Control, 6));
510 }
511
512 #[test]
513 fn test_combined_lines() {
514 let lines: [&[u8]; 3] = [b"a, b", b"", b"c"];
515 let c = combined(lines, &Limits::DEFAULT).unwrap();
516 assert_eq!(c.len(), 3);
517 assert_eq!(c.iter().collect::<Vec<_>>(), [&b"a"[..], b"b", b"c"]);
518 assert_eq!(c.iter().len(), 3);
519 let bad: [&[u8]; 2] = [b"a", b"\"open"];
520 let e = combined(bad, &Limits::DEFAULT).unwrap_err();
521 assert_eq!((e.kind(), e.line()), (ErrorKind::UnterminatedQuote, 1));
522 let empty: [&[u8]; 0] = [];
523 assert!(combined(empty, &Limits::DEFAULT).unwrap().is_empty());
524 }
525
526 #[test]
527 fn test_combined_budgets_count_across_lines() {
528 let l = Limits::DEFAULT.max_list_members(2);
529 let lines: [&[u8]; 2] = [b"a", b"b, c"];
530 let e = combined(lines, &l).unwrap_err();
531 assert_eq!(
532 (e.kind(), e.line(), e.offset()),
533 (ErrorKind::TooManyMembers, 1, 3)
534 );
535 }
536
537 #[test]
538 fn test_combined_equals_joined_at_every_comma() {
539 let value = b"a, \"b,c\", (d,e), f;g=\"h\", i";
540 let whole = all(value);
541 let mut commas = Vec::new();
542 let mut from = 0;
543 while let Some(i) = top_level_comma(&value[from..]) {
544 commas.push(from + i);
545 from += i + 1;
546 }
547 assert_eq!(commas.len(), 4);
548 for &split in &commas {
549 let lines: [&[u8]; 2] = [&value[..split], &value[split + 1..]];
550 let c = combined(lines, &Limits::DEFAULT).unwrap();
551 assert_eq!(c.iter().collect::<Vec<_>>(), whole, "split at {split}");
552 }
553 }
554
555 #[test]
556 fn test_contains_token_fails_closed() {
557 assert!(contains_token(
558 [&b"close"[..], b"keep-alive, Upgrade"],
559 b"upgrade"
560 ));
561 assert!(!contains_token([&b"upgrade\r"[..]], b"upgrade"));
562 let none: [&[u8]; 0] = [];
563 assert!(!contains_token(none, b"x"));
564 }
565
566 #[test]
567 fn test_for_each_sees_every_element() {
568 let mut seen = vec![];
569 let n = for_each(b"a, b ,c", &Limits::DEFAULT, |e| seen.push(e)).unwrap();
570 assert_eq!((n, seen), (3, vec![&b"a"[..], b"b", b"c"]));
571 }
572}