1use crate::{
20 error::{ErrorKind, ParseError, Result, WriteError},
21 limits::Limits,
22 num::parse_u32_saturating,
23 scan::control_kind,
24 text::{Word, is_token, parse_word, skip_ows, token_len, trim_ows},
25 write::{Sink, reserve, write_decimal_u64},
26};
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub struct Directive<'a> {
42 pub name: &'a [u8],
44 pub value: Option<Word<'a>>,
46}
47
48#[inline]
50fn skip_ws(mut s: &[u8]) -> &[u8] {
51 while let [b' ' | b'\t', rest @ ..] = s {
52 s = rest;
53 }
54 s
55}
56
57fn directive(s: &[u8]) -> Result<(Directive<'_>, &[u8])> {
60 let n = token_len(s);
61 if n == 0 {
62 return Err(bad(s, 0));
63 }
64 let (name, after) = s.split_at(n);
65 let [b'=', value @ ..] = after else {
66 return Ok((Directive { name, value: None }, after));
67 };
68 let (w, used) = parse_word(value).map_err(|x| match x.kind() {
69 ErrorKind::Empty => bad(s, n + 1),
70 _ => x.shifted(n + 1),
71 })?;
72 Ok((
73 Directive {
74 name,
75 value: Some(w),
76 },
77 value.get(used..).unwrap_or(&[]),
78 ))
79}
80
81#[derive(Default)]
83struct Budget {
84 count: usize,
85 empty: usize,
86}
87
88fn walk<'a>(
93 v: &'a [u8],
94 limits: &Limits,
95 budget: &mut Budget,
96 mut f: impl FnMut(Directive<'a>),
97) -> Result<()> {
98 let at = |rest: &[u8]| v.len() - rest.len();
99 let mut rest = v;
100 let mut after_comma = false;
101 loop {
102 rest = skip_ws(rest);
103 match rest {
104 [] if !after_comma => return Ok(()),
105 [] | [b',', ..] => {
106 budget.empty += 1;
107 if budget.empty > usize::from(limits.empty_elements) {
108 return Err(ParseError::new(ErrorKind::TooManyEmptyElements, at(rest)));
109 }
110 let [_, tail @ ..] = rest else {
111 return Ok(());
112 };
113 rest = tail;
114 after_comma = true;
115 continue;
116 }
117 _ => {}
118 }
119 let (d, tail) = directive(rest).map_err(|e| e.shifted(at(rest)))?;
120 budget.count += 1;
121 if budget.count > usize::from(limits.list_members) {
122 return Err(ParseError::new(ErrorKind::TooManyMembers, at(rest)));
123 }
124 f(d);
125 rest = skip_ws(tail);
126 match rest {
127 [] => return Ok(()),
128 [b',', tail @ ..] => {
129 rest = tail;
130 after_comma = true;
131 }
132 _ => return Err(bad(rest, 0).shifted(at(rest))),
133 }
134 }
135}
136
137fn walk_value<'a>(
139 value: &'a [u8],
140 limits: &Limits,
141 budget: &mut Budget,
142 f: impl FnMut(Directive<'a>),
143) -> Result<&'a [u8]> {
144 let lead = skip_ows(value);
145 let v = trim_ows(value);
146 walk(v, limits, budget, f).map_err(|e| e.shifted(lead))?;
147 Ok(v)
148}
149
150fn bad(e: &[u8], at: usize) -> ParseError {
151 match e.get(at) {
152 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
153 _ => ParseError::new(ErrorKind::NotToken, at),
154 }
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
170pub struct Directives<'a> {
171 text: &'a [u8],
173 count: usize,
174}
175
176impl<'a> Directives<'a> {
177 pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
190 DirectiveIter { s: self.text }
191 }
192
193 #[must_use]
204 pub const fn len(&self) -> usize {
205 self.count
206 }
207
208 #[must_use]
220 pub const fn is_empty(&self) -> bool {
221 self.count == 0
222 }
223}
224
225struct DirectiveIter<'a> {
227 s: &'a [u8],
228}
229
230impl<'a> Iterator for DirectiveIter<'a> {
231 type Item = Directive<'a>;
232
233 fn next(&mut self) -> Option<Directive<'a>> {
234 let mut rest = self.s;
235 while let [b' ' | b'\t' | b',', tail @ ..] = rest {
236 rest = tail;
237 }
238 let (d, tail) = directive(rest).ok()?;
239 self.s = tail;
240 Some(d)
241 }
242}
243
244pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
263 let mut budget = Budget::default();
264 let text = walk_value(value, limits, &mut budget, |_| {})?;
265 let count = budget.count;
266 Ok(Directives { text, count })
267}
268
269#[non_exhaustive]
288#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
289pub struct ResponseDirectives {
290 pub max_age: Option<u32>,
292 pub s_maxage: Option<u32>,
294 pub no_cache: bool,
296 pub no_store: bool,
298 pub private: bool,
300 pub public: bool,
302 pub must_revalidate: bool,
304 pub proxy_revalidate: bool,
306 pub must_understand: bool,
308 pub no_transform: bool,
310 pub immutable: bool,
312 pub stale_while_revalidate: Option<u32>,
314 pub stale_if_error: Option<u32>,
316 pub duplicates: bool,
318 pub invalid_values: bool,
320 pub unknown: u16,
322}
323
324#[non_exhaustive]
335#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
336pub struct RequestDirectives {
337 pub max_age: Option<u32>,
339 pub max_stale: Option<Option<u32>>,
341 pub min_fresh: Option<u32>,
343 pub no_cache: bool,
345 pub no_store: bool,
347 pub no_transform: bool,
349 pub only_if_cached: bool,
351 pub stale_if_error: Option<u32>,
353 pub duplicates: bool,
355 pub invalid_values: bool,
357 pub unknown: u16,
359}
360
361fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
364 let n = match value {
368 Some(Word::Token(t)) => parse_u32_saturating(t).ok(),
369 Some(Word::Quoted(q)) => match q.as_plain() {
370 Some(p) => parse_u32_saturating(p).ok(),
371 None => {
372 let mut n: Option<u32> = None;
373 for b in q.bytes() {
374 if !b.is_ascii_digit() {
375 n = None;
376 break;
377 }
378 n = Some(
379 n.unwrap_or(0)
380 .saturating_mul(10)
381 .saturating_add(u32::from(b - b'0'))
382 .min(crate::num::DELTA_SECONDS_MAX),
383 );
384 }
385 n
386 }
387 },
388 None => None,
389 };
390 if n.is_none() {
391 *invalid = true;
392 }
393 n
394}
395
396fn flag(
399 slot: &mut bool,
400 seen: &mut bool,
401 dup: &mut bool,
402 value: Option<Word<'_>>,
403 invalid: &mut bool,
404 list_ok: bool,
405) {
406 if *seen {
407 *dup = true;
408 return;
409 }
410 *seen = true;
411 *slot = true;
412 if value.is_some() && !list_ok {
413 *invalid = true;
414 }
415}
416
417fn number(
419 slot: &mut Option<u32>,
420 seen: &mut bool,
421 dup: &mut bool,
422 value: Option<Word<'_>>,
423 invalid: &mut bool,
424) {
425 if *seen {
426 *dup = true;
427 return;
428 }
429 *seen = true;
430 *slot = seconds(value, invalid);
431}
432
433#[inline]
440fn is_name<const N: usize>(n: &[u8], lower: &[u8; N]) -> bool {
441 let Ok(a) = <&[u8; N]>::try_from(n) else {
442 return false;
443 };
444 if N < 4 {
445 return a.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p);
446 }
447 if N < 8 {
448 let w = |s: &[u8; N], at: usize| {
449 s.get(at..at + 4)
450 .and_then(|c| <[u8; 4]>::try_from(c).ok())
451 .map_or(0, u32::from_le_bytes)
452 };
453 return w(a, 0) | 0x2020_2020 == w(lower, 0)
454 && w(a, N - 4) | 0x2020_2020 == w(lower, N - 4);
455 }
456 let w = |s: &[u8; N], at: usize| {
457 s.get(at..at + 8)
458 .and_then(|c| <[u8; 8]>::try_from(c).ok())
459 .map_or(0, u64::from_le_bytes)
460 };
461 let mut at = 0;
462 loop {
463 let i = at.min(N - 8);
464 if w(a, i) | 0x2020_2020_2020_2020 != w(lower, i) {
465 return false;
466 }
467 at += 8;
468 if at >= N {
469 return true;
470 }
471 }
472}
473
474fn response_slot(n: &[u8]) -> Option<usize> {
476 match n.len() {
477 6 if is_name(n, b"public") => Some(5),
478 7 if is_name(n, b"max-age") => Some(0),
479 7 if is_name(n, b"private") => Some(4),
480 8 if is_name(n, b"s-maxage") => Some(1),
481 8 if is_name(n, b"no-cache") => Some(2),
482 8 if is_name(n, b"no-store") => Some(3),
483 9 if is_name(n, b"immutable") => Some(10),
484 12 if is_name(n, b"no-transform") => Some(9),
485 14 if is_name(n, b"stale-if-error") => Some(12),
486 15 if is_name(n, b"must-revalidate") => Some(6),
487 15 if is_name(n, b"must-understand") => Some(8),
488 16 if is_name(n, b"proxy-revalidate") => Some(7),
489 22 if is_name(n, b"stale-while-revalidate") => Some(11),
490 _ => None,
491 }
492}
493
494#[derive(Default)]
496struct ResponseBuilder {
497 r: ResponseDirectives,
498 seen: [bool; 13],
499}
500
501impl ResponseBuilder {
502 fn push(&mut self, d: Directive<'_>) {
503 let r = &mut self.r;
504 let Some(slot) = response_slot(d.name) else {
505 r.unknown = r.unknown.saturating_add(1);
506 return;
507 };
508 let Some(seen) = self.seen.get_mut(slot) else {
509 return;
510 };
511 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
512 match slot {
513 0 => number(&mut r.max_age, seen, dup, d.value, inv),
514 1 => number(&mut r.s_maxage, seen, dup, d.value, inv),
515 2 => flag(&mut r.no_cache, seen, dup, d.value, inv, true),
516 3 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
517 4 => flag(&mut r.private, seen, dup, d.value, inv, true),
518 5 => flag(&mut r.public, seen, dup, d.value, inv, false),
519 6 => flag(&mut r.must_revalidate, seen, dup, d.value, inv, false),
520 7 => flag(&mut r.proxy_revalidate, seen, dup, d.value, inv, false),
521 8 => flag(&mut r.must_understand, seen, dup, d.value, inv, false),
522 9 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
523 10 => flag(&mut r.immutable, seen, dup, d.value, inv, false),
524 11 => number(&mut r.stale_while_revalidate, seen, dup, d.value, inv),
525 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
526 }
527 }
528}
529
530fn request_slot(n: &[u8]) -> Option<usize> {
532 match n.len() {
533 7 if is_name(n, b"max-age") => Some(0),
534 8 if is_name(n, b"no-cache") => Some(3),
535 8 if is_name(n, b"no-store") => Some(4),
536 9 if is_name(n, b"max-stale") => Some(1),
537 9 if is_name(n, b"min-fresh") => Some(2),
538 12 if is_name(n, b"no-transform") => Some(5),
539 14 if is_name(n, b"only-if-cached") => Some(6),
540 14 if is_name(n, b"stale-if-error") => Some(7),
541 _ => None,
542 }
543}
544
545#[derive(Default)]
547struct RequestBuilder {
548 r: RequestDirectives,
549 seen: [bool; 8],
550}
551
552impl RequestBuilder {
553 fn push(&mut self, d: Directive<'_>) {
554 let r = &mut self.r;
555 let Some(slot) = request_slot(d.name) else {
556 r.unknown = r.unknown.saturating_add(1);
557 return;
558 };
559 let Some(seen) = self.seen.get_mut(slot) else {
560 return;
561 };
562 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
563 match slot {
564 0 => number(&mut r.max_age, seen, dup, d.value, inv),
565 1 => {
566 if *seen {
567 *dup = true;
568 } else {
569 *seen = true;
570 r.max_stale = match d.value {
573 None => Some(None),
574 v => seconds(v, inv).map(Some),
575 };
576 }
577 }
578 2 => number(&mut r.min_fresh, seen, dup, d.value, inv),
579 3 => flag(&mut r.no_cache, seen, dup, d.value, inv, false),
580 4 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
581 5 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
582 6 => flag(&mut r.only_if_cached, seen, dup, d.value, inv, false),
583 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
584 }
585 }
586}
587
588pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
604 let mut b = ResponseBuilder::default();
605 let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
606 b.push(d)
607 })?;
608 Ok(b.r)
609}
610
611pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
626 let mut b = RequestBuilder::default();
627 let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
628 b.push(d)
629 })?;
630 Ok(b.r)
631}
632
633pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
651where
652 I: IntoIterator<Item = &'a [u8]>,
653 I::IntoIter: Clone,
654{
655 let (mut b, mut budget) = (ResponseBuilder::default(), Budget::default());
656 for (n, line) in lines.into_iter().enumerate() {
657 let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
658 .map_err(|e| e.on_line(n))?;
659 }
660 Ok(b.r)
661}
662
663pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
681where
682 I: IntoIterator<Item = &'a [u8]>,
683 I::IntoIter: Clone,
684{
685 let (mut b, mut budget) = (RequestBuilder::default(), Budget::default());
686 for (n, line) in lines.into_iter().enumerate() {
687 let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
688 .map_err(|e| e.on_line(n))?;
689 }
690 Ok(b.r)
691}
692
693pub struct Writer<'s, S: Sink> {
710 out: &'s mut S,
711 first: bool,
712}
713
714impl<'s, S: Sink> Writer<'s, S> {
715 pub fn new(out: &'s mut S) -> Self {
730 Self { out, first: true }
731 }
732
733 fn separator(&self) -> &'static [u8] {
734 if self.first { b"" } else { b", " }
735 }
736
737 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
757 if !is_token(name.as_bytes()) {
758 return Err(WriteError::NotToken);
759 }
760 let sep = self.separator();
761 reserve(self.out, sep.len() + name.len())?;
762 self.out.put(sep)?;
763 self.out.put(name.as_bytes())?;
764 self.first = false;
765 Ok(self)
766 }
767
768 pub fn seconds(
786 &mut self,
787 name: &str,
788 secs: u32,
789 ) -> core::result::Result<&mut Self, WriteError> {
790 if !is_token(name.as_bytes()) {
791 return Err(WriteError::NotToken);
792 }
793 let mut digits = crate::write::CountSink::new();
794 write_decimal_u64(u64::from(secs), &mut digits)?;
795 let sep = self.separator();
796 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
797 self.out.put(sep)?;
798 self.out.put(name.as_bytes())?;
799 self.out.put(b"=")?;
800 write_decimal_u64(u64::from(secs), self.out)?;
801 self.first = false;
802 Ok(self)
803 }
804
805 pub fn fields(
823 &mut self,
824 name: &str,
825 fields: &[&str],
826 ) -> core::result::Result<&mut Self, WriteError> {
827 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
828 return Err(WriteError::NotToken);
829 }
830 let sep = self.separator();
831 let list_len: usize =
832 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
833 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
834 self.out.put(sep)?;
835 self.out.put(name.as_bytes())?;
836 self.out.put(b"=\"")?;
837 for (i, f) in fields.iter().enumerate() {
838 if i > 0 {
839 self.out.put(b", ")?;
840 }
841 self.out.put(f.as_bytes())?;
842 }
843 self.out.put(b"\"")?;
844 self.first = false;
845 Ok(self)
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
854 fn test_response_directives_cases() {
855 let r = |v: &[u8]| response_cache_control(v).unwrap();
856 assert_eq!(r(b"max-age=0").max_age, Some(0));
857 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
858 let bad = r(b"max-age=60s");
859 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
860 assert_eq!(
861 r(b"max-age=99999999999999999999").max_age,
862 Some(2_147_483_648)
863 );
864 let dup = r(b"max-age=1, max-age=2");
865 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
866 let nc = r(b"no-cache=\"Set-Cookie\"");
867 assert!(nc.no_cache && !nc.invalid_values);
868 let both = r(b"private, public");
869 assert!(both.private && both.public);
870 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
871 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
872 assert_eq!(
873 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
874 Some(60)
875 );
876 assert!(r(b"no-store=1").invalid_values);
877 assert!(r(b"max-age").invalid_values);
878 assert_eq!(r(b"").unknown, 0);
879 }
880
881 #[test]
882 fn test_request_directives_cases() {
883 let q = |v: &[u8]| request_cache_control(v).unwrap();
884 assert_eq!(q(b"max-stale").max_stale, Some(None));
885 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
886 assert!(q(b"only-if-cached").only_if_cached);
887 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
888 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
889 }
890
891 #[test]
892 fn test_cache_structure_errors() {
893 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
894 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
895 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
896 assert_eq!(k(b"=5"), ErrorKind::NotToken);
897 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
898 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
899 }
900
901 #[test]
902 fn test_lines() {
903 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
904 let r = response_lines(lines).unwrap();
905 assert!(r.public && r.max_age == Some(5));
906 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
907 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
908 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
909 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
910 }
911
912 #[test]
913 fn test_writer_round_trip_and_refusals() {
914 let mut buf = [0u8; 128];
915 let mut out = crate::SliceSink::new(&mut buf);
916 Writer::new(&mut out)
917 .flag("public")
918 .and_then(|w| w.seconds("max-age", u32::MAX))
919 .and_then(|w| w.fields("private", &["X-A"]))
920 .map(|_| ())
921 .unwrap();
922 let r = response_cache_control(out.written()).unwrap();
923 assert!(r.public && r.private);
924 assert_eq!(r.max_age, Some(2_147_483_648));
925 let mut w = Writer::new(&mut out);
926 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
927 assert!(matches!(
928 w.fields("no-cache", &["a b"]),
929 Err(WriteError::NotToken)
930 ));
931 let mut small = [0u8; 3];
932 let mut out = crate::SliceSink::new(&mut small);
933 assert!(matches!(
934 Writer::new(&mut out).flag("public"),
935 Err(WriteError::Full)
936 ));
937 assert!(out.is_empty());
938 }
939
940 #[test]
944 fn test_is_name_matches_bytewise() {
945 fn check<const N: usize>(lower: &[u8; N]) {
946 let reference =
947 |n: &[u8]| n.len() == N && n.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p);
948 let mut n = *lower;
949 for i in 0..N {
950 for b in 0..=255u8 {
951 n[i] = b;
952 assert_eq!(is_name(&n, lower), reference(&n), "{lower:?} at {i} = {b}");
953 }
954 n[i] = lower[i].to_ascii_uppercase();
955 assert!(is_name(&n, lower));
956 }
957 assert!(!is_name(&lower[..N - 1], lower));
958 let mut long = lower.to_vec();
959 long.push(b'a');
960 assert!(!is_name(&long, lower));
961 }
962 check(b"abc");
963 check(b"public");
964 check(b"max-age");
965 check(b"private");
966 check(b"s-maxage");
967 check(b"no-cache");
968 check(b"no-store");
969 check(b"immutable");
970 check(b"max-stale");
971 check(b"min-fresh");
972 check(b"no-transform");
973 check(b"only-if-cached");
974 check(b"stale-if-error");
975 check(b"must-revalidate");
976 check(b"must-understand");
977 check(b"proxy-revalidate");
978 check(b"stale-while-revalidate");
979 }
980}