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
433fn is_name(n: &[u8], lower: &[u8]) -> bool {
438 n.len() == lower.len() && n.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p)
439}
440
441fn response_slot(n: &[u8]) -> Option<usize> {
443 let is = |s: &[u8]| is_name(n, s);
444 match n.len() {
445 6 if is(b"public") => Some(5),
446 7 if is(b"max-age") => Some(0),
447 7 if is(b"private") => Some(4),
448 8 if is(b"s-maxage") => Some(1),
449 8 if is(b"no-cache") => Some(2),
450 8 if is(b"no-store") => Some(3),
451 9 if is(b"immutable") => Some(10),
452 12 if is(b"no-transform") => Some(9),
453 14 if is(b"stale-if-error") => Some(12),
454 15 if is(b"must-revalidate") => Some(6),
455 15 if is(b"must-understand") => Some(8),
456 16 if is(b"proxy-revalidate") => Some(7),
457 22 if is(b"stale-while-revalidate") => Some(11),
458 _ => None,
459 }
460}
461
462#[derive(Default)]
464struct ResponseBuilder {
465 r: ResponseDirectives,
466 seen: [bool; 13],
467}
468
469impl ResponseBuilder {
470 fn push(&mut self, d: Directive<'_>) {
471 let r = &mut self.r;
472 let Some(slot) = response_slot(d.name) else {
473 r.unknown = r.unknown.saturating_add(1);
474 return;
475 };
476 let Some(seen) = self.seen.get_mut(slot) else {
477 return;
478 };
479 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
480 match slot {
481 0 => number(&mut r.max_age, seen, dup, d.value, inv),
482 1 => number(&mut r.s_maxage, seen, dup, d.value, inv),
483 2 => flag(&mut r.no_cache, seen, dup, d.value, inv, true),
484 3 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
485 4 => flag(&mut r.private, seen, dup, d.value, inv, true),
486 5 => flag(&mut r.public, seen, dup, d.value, inv, false),
487 6 => flag(&mut r.must_revalidate, seen, dup, d.value, inv, false),
488 7 => flag(&mut r.proxy_revalidate, seen, dup, d.value, inv, false),
489 8 => flag(&mut r.must_understand, seen, dup, d.value, inv, false),
490 9 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
491 10 => flag(&mut r.immutable, seen, dup, d.value, inv, false),
492 11 => number(&mut r.stale_while_revalidate, seen, dup, d.value, inv),
493 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
494 }
495 }
496}
497
498fn request_slot(n: &[u8]) -> Option<usize> {
500 let is = |s: &[u8]| is_name(n, s);
501 match n.len() {
502 7 if is(b"max-age") => Some(0),
503 8 if is(b"no-cache") => Some(3),
504 8 if is(b"no-store") => Some(4),
505 9 if is(b"max-stale") => Some(1),
506 9 if is(b"min-fresh") => Some(2),
507 12 if is(b"no-transform") => Some(5),
508 14 if is(b"only-if-cached") => Some(6),
509 14 if is(b"stale-if-error") => Some(7),
510 _ => None,
511 }
512}
513
514#[derive(Default)]
516struct RequestBuilder {
517 r: RequestDirectives,
518 seen: [bool; 8],
519}
520
521impl RequestBuilder {
522 fn push(&mut self, d: Directive<'_>) {
523 let r = &mut self.r;
524 let Some(slot) = request_slot(d.name) else {
525 r.unknown = r.unknown.saturating_add(1);
526 return;
527 };
528 let Some(seen) = self.seen.get_mut(slot) else {
529 return;
530 };
531 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
532 match slot {
533 0 => number(&mut r.max_age, seen, dup, d.value, inv),
534 1 => {
535 if *seen {
536 *dup = true;
537 } else {
538 *seen = true;
539 r.max_stale = match d.value {
542 None => Some(None),
543 v => seconds(v, inv).map(Some),
544 };
545 }
546 }
547 2 => number(&mut r.min_fresh, seen, dup, d.value, inv),
548 3 => flag(&mut r.no_cache, seen, dup, d.value, inv, false),
549 4 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
550 5 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
551 6 => flag(&mut r.only_if_cached, seen, dup, d.value, inv, false),
552 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
553 }
554 }
555}
556
557pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
573 let mut b = ResponseBuilder::default();
574 let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
575 b.push(d)
576 })?;
577 Ok(b.r)
578}
579
580pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
595 let mut b = RequestBuilder::default();
596 let _ = walk_value(value, &Limits::DEFAULT, &mut Budget::default(), |d| {
597 b.push(d)
598 })?;
599 Ok(b.r)
600}
601
602pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
620where
621 I: IntoIterator<Item = &'a [u8]>,
622 I::IntoIter: Clone,
623{
624 let (mut b, mut budget) = (ResponseBuilder::default(), Budget::default());
625 for (n, line) in lines.into_iter().enumerate() {
626 let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
627 .map_err(|e| e.on_line(n))?;
628 }
629 Ok(b.r)
630}
631
632pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
650where
651 I: IntoIterator<Item = &'a [u8]>,
652 I::IntoIter: Clone,
653{
654 let (mut b, mut budget) = (RequestBuilder::default(), Budget::default());
655 for (n, line) in lines.into_iter().enumerate() {
656 let _ = walk_value(line, &Limits::DEFAULT, &mut budget, |d| b.push(d))
657 .map_err(|e| e.on_line(n))?;
658 }
659 Ok(b.r)
660}
661
662pub struct Writer<'s, S: Sink> {
679 out: &'s mut S,
680 first: bool,
681}
682
683impl<'s, S: Sink> Writer<'s, S> {
684 pub fn new(out: &'s mut S) -> Self {
699 Self { out, first: true }
700 }
701
702 fn separator(&self) -> &'static [u8] {
703 if self.first { b"" } else { b", " }
704 }
705
706 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
726 if !is_token(name.as_bytes()) {
727 return Err(WriteError::NotToken);
728 }
729 let sep = self.separator();
730 reserve(self.out, sep.len() + name.len())?;
731 self.out.put(sep)?;
732 self.out.put(name.as_bytes())?;
733 self.first = false;
734 Ok(self)
735 }
736
737 pub fn seconds(
755 &mut self,
756 name: &str,
757 secs: u32,
758 ) -> core::result::Result<&mut Self, WriteError> {
759 if !is_token(name.as_bytes()) {
760 return Err(WriteError::NotToken);
761 }
762 let mut digits = crate::write::CountSink::new();
763 write_decimal_u64(u64::from(secs), &mut digits)?;
764 let sep = self.separator();
765 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
766 self.out.put(sep)?;
767 self.out.put(name.as_bytes())?;
768 self.out.put(b"=")?;
769 write_decimal_u64(u64::from(secs), self.out)?;
770 self.first = false;
771 Ok(self)
772 }
773
774 pub fn fields(
792 &mut self,
793 name: &str,
794 fields: &[&str],
795 ) -> core::result::Result<&mut Self, WriteError> {
796 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
797 return Err(WriteError::NotToken);
798 }
799 let sep = self.separator();
800 let list_len: usize =
801 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
802 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
803 self.out.put(sep)?;
804 self.out.put(name.as_bytes())?;
805 self.out.put(b"=\"")?;
806 for (i, f) in fields.iter().enumerate() {
807 if i > 0 {
808 self.out.put(b", ")?;
809 }
810 self.out.put(f.as_bytes())?;
811 }
812 self.out.put(b"\"")?;
813 self.first = false;
814 Ok(self)
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821
822 #[test]
823 fn test_response_directives_cases() {
824 let r = |v: &[u8]| response_cache_control(v).unwrap();
825 assert_eq!(r(b"max-age=0").max_age, Some(0));
826 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
827 let bad = r(b"max-age=60s");
828 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
829 assert_eq!(
830 r(b"max-age=99999999999999999999").max_age,
831 Some(2_147_483_648)
832 );
833 let dup = r(b"max-age=1, max-age=2");
834 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
835 let nc = r(b"no-cache=\"Set-Cookie\"");
836 assert!(nc.no_cache && !nc.invalid_values);
837 let both = r(b"private, public");
838 assert!(both.private && both.public);
839 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
840 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
841 assert_eq!(
842 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
843 Some(60)
844 );
845 assert!(r(b"no-store=1").invalid_values);
846 assert!(r(b"max-age").invalid_values);
847 assert_eq!(r(b"").unknown, 0);
848 }
849
850 #[test]
851 fn test_request_directives_cases() {
852 let q = |v: &[u8]| request_cache_control(v).unwrap();
853 assert_eq!(q(b"max-stale").max_stale, Some(None));
854 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
855 assert!(q(b"only-if-cached").only_if_cached);
856 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
857 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
858 }
859
860 #[test]
861 fn test_cache_structure_errors() {
862 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
863 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
864 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
865 assert_eq!(k(b"=5"), ErrorKind::NotToken);
866 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
867 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
868 }
869
870 #[test]
871 fn test_lines() {
872 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
873 let r = response_lines(lines).unwrap();
874 assert!(r.public && r.max_age == Some(5));
875 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
876 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
877 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
878 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
879 }
880
881 #[test]
882 fn test_writer_round_trip_and_refusals() {
883 let mut buf = [0u8; 128];
884 let mut out = crate::SliceSink::new(&mut buf);
885 Writer::new(&mut out)
886 .flag("public")
887 .and_then(|w| w.seconds("max-age", u32::MAX))
888 .and_then(|w| w.fields("private", &["X-A"]))
889 .map(|_| ())
890 .unwrap();
891 let r = response_cache_control(out.written()).unwrap();
892 assert!(r.public && r.private);
893 assert_eq!(r.max_age, Some(2_147_483_648));
894 let mut w = Writer::new(&mut out);
895 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
896 assert!(matches!(
897 w.fields("no-cache", &["a b"]),
898 Err(WriteError::NotToken)
899 ));
900 let mut small = [0u8; 3];
901 let mut out = crate::SliceSink::new(&mut small);
902 assert!(matches!(
903 Writer::new(&mut out).flag("public"),
904 Err(WriteError::Full)
905 ));
906 assert!(out.is_empty());
907 }
908}