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
81fn walk<'a>(v: &'a [u8], limits: &Limits, mut f: impl FnMut(Directive<'a>)) -> Result<usize> {
86 let at = |rest: &[u8]| v.len() - rest.len();
87 let (mut rest, mut count, mut empty) = (v, 0usize, 0usize);
88 loop {
89 rest = skip_ws(rest);
90 match rest {
91 [] => return Ok(count),
92 [b',', tail @ ..] => {
93 empty += 1;
94 if empty > usize::from(limits.empty_elements) {
95 return Err(ParseError::new(ErrorKind::TooManyEmptyElements, at(rest)));
96 }
97 rest = tail;
98 continue;
99 }
100 _ => {}
101 }
102 let (d, tail) = directive(rest).map_err(|e| e.shifted(at(rest)))?;
103 count += 1;
104 if count > usize::from(limits.list_members) {
105 return Err(ParseError::new(ErrorKind::TooManyMembers, at(rest)));
106 }
107 f(d);
108 rest = skip_ws(tail);
109 match rest {
110 [] => return Ok(count),
111 [b',', tail @ ..] => rest = tail,
112 _ => return Err(bad(rest, 0).shifted(at(rest))),
113 }
114 }
115}
116
117fn walk_value<'a>(
119 value: &'a [u8],
120 limits: &Limits,
121 f: impl FnMut(Directive<'a>),
122) -> Result<(&'a [u8], usize)> {
123 let lead = skip_ows(value);
124 let v = trim_ows(value);
125 let count = walk(v, limits, f).map_err(|e| e.shifted(lead))?;
126 Ok((v, count))
127}
128
129fn bad(e: &[u8], at: usize) -> ParseError {
130 match e.get(at) {
131 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
132 _ => ParseError::new(ErrorKind::NotToken, at),
133 }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
149pub struct Directives<'a> {
150 text: &'a [u8],
152 count: usize,
153}
154
155impl<'a> Directives<'a> {
156 pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
169 DirectiveIter { s: self.text }
170 }
171
172 #[must_use]
183 pub const fn len(&self) -> usize {
184 self.count
185 }
186
187 #[must_use]
199 pub const fn is_empty(&self) -> bool {
200 self.count == 0
201 }
202}
203
204struct DirectiveIter<'a> {
206 s: &'a [u8],
207}
208
209impl<'a> Iterator for DirectiveIter<'a> {
210 type Item = Directive<'a>;
211
212 fn next(&mut self) -> Option<Directive<'a>> {
213 let mut rest = self.s;
214 while let [b' ' | b'\t' | b',', tail @ ..] = rest {
215 rest = tail;
216 }
217 let (d, tail) = directive(rest).ok()?;
218 self.s = tail;
219 Some(d)
220 }
221}
222
223pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
242 let (text, count) = walk_value(value, limits, |_| {})?;
243 Ok(Directives { text, count })
244}
245
246#[non_exhaustive]
265#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
266pub struct ResponseDirectives {
267 pub max_age: Option<u32>,
269 pub s_maxage: Option<u32>,
271 pub no_cache: bool,
273 pub no_store: bool,
275 pub private: bool,
277 pub public: bool,
279 pub must_revalidate: bool,
281 pub proxy_revalidate: bool,
283 pub must_understand: bool,
285 pub no_transform: bool,
287 pub immutable: bool,
289 pub stale_while_revalidate: Option<u32>,
291 pub stale_if_error: Option<u32>,
293 pub duplicates: bool,
295 pub invalid_values: bool,
297 pub unknown: u16,
299}
300
301#[non_exhaustive]
312#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
313pub struct RequestDirectives {
314 pub max_age: Option<u32>,
316 pub max_stale: Option<Option<u32>>,
318 pub min_fresh: Option<u32>,
320 pub no_cache: bool,
322 pub no_store: bool,
324 pub no_transform: bool,
326 pub only_if_cached: bool,
328 pub stale_if_error: Option<u32>,
330 pub duplicates: bool,
332 pub invalid_values: bool,
334 pub unknown: u16,
336}
337
338fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
341 let n = match value {
345 Some(Word::Token(t)) => parse_u32_saturating(t).ok(),
346 Some(Word::Quoted(q)) => match q.as_plain() {
347 Some(p) => parse_u32_saturating(p).ok(),
348 None => {
349 let mut n: Option<u32> = None;
350 for b in q.bytes() {
351 if !b.is_ascii_digit() {
352 n = None;
353 break;
354 }
355 n = Some(
356 n.unwrap_or(0)
357 .saturating_mul(10)
358 .saturating_add(u32::from(b - b'0'))
359 .min(crate::num::DELTA_SECONDS_MAX),
360 );
361 }
362 n
363 }
364 },
365 None => None,
366 };
367 if n.is_none() {
368 *invalid = true;
369 }
370 n
371}
372
373fn flag(
376 slot: &mut bool,
377 seen: &mut bool,
378 dup: &mut bool,
379 value: Option<Word<'_>>,
380 invalid: &mut bool,
381 list_ok: bool,
382) {
383 if *seen {
384 *dup = true;
385 return;
386 }
387 *seen = true;
388 *slot = true;
389 if value.is_some() && !list_ok {
390 *invalid = true;
391 }
392}
393
394fn number(
396 slot: &mut Option<u32>,
397 seen: &mut bool,
398 dup: &mut bool,
399 value: Option<Word<'_>>,
400 invalid: &mut bool,
401) {
402 if *seen {
403 *dup = true;
404 return;
405 }
406 *seen = true;
407 *slot = seconds(value, invalid);
408}
409
410fn is_name(n: &[u8], lower: &[u8]) -> bool {
415 n.len() == lower.len() && n.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p)
416}
417
418fn response_slot(n: &[u8]) -> Option<usize> {
420 let is = |s: &[u8]| is_name(n, s);
421 match n.len() {
422 6 if is(b"public") => Some(5),
423 7 if is(b"max-age") => Some(0),
424 7 if is(b"private") => Some(4),
425 8 if is(b"s-maxage") => Some(1),
426 8 if is(b"no-cache") => Some(2),
427 8 if is(b"no-store") => Some(3),
428 9 if is(b"immutable") => Some(10),
429 12 if is(b"no-transform") => Some(9),
430 14 if is(b"stale-if-error") => Some(12),
431 15 if is(b"must-revalidate") => Some(6),
432 15 if is(b"must-understand") => Some(8),
433 16 if is(b"proxy-revalidate") => Some(7),
434 22 if is(b"stale-while-revalidate") => Some(11),
435 _ => None,
436 }
437}
438
439#[derive(Default)]
441struct ResponseBuilder {
442 r: ResponseDirectives,
443 seen: [bool; 13],
444}
445
446impl ResponseBuilder {
447 fn push(&mut self, d: Directive<'_>) {
448 let r = &mut self.r;
449 let Some(slot) = response_slot(d.name) else {
450 r.unknown = r.unknown.saturating_add(1);
451 return;
452 };
453 let Some(seen) = self.seen.get_mut(slot) else {
454 return;
455 };
456 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
457 match slot {
458 0 => number(&mut r.max_age, seen, dup, d.value, inv),
459 1 => number(&mut r.s_maxage, seen, dup, d.value, inv),
460 2 => flag(&mut r.no_cache, seen, dup, d.value, inv, true),
461 3 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
462 4 => flag(&mut r.private, seen, dup, d.value, inv, true),
463 5 => flag(&mut r.public, seen, dup, d.value, inv, false),
464 6 => flag(&mut r.must_revalidate, seen, dup, d.value, inv, false),
465 7 => flag(&mut r.proxy_revalidate, seen, dup, d.value, inv, false),
466 8 => flag(&mut r.must_understand, seen, dup, d.value, inv, false),
467 9 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
468 10 => flag(&mut r.immutable, seen, dup, d.value, inv, false),
469 11 => number(&mut r.stale_while_revalidate, seen, dup, d.value, inv),
470 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
471 }
472 }
473}
474
475fn request_slot(n: &[u8]) -> Option<usize> {
477 let is = |s: &[u8]| is_name(n, s);
478 match n.len() {
479 7 if is(b"max-age") => Some(0),
480 8 if is(b"no-cache") => Some(3),
481 8 if is(b"no-store") => Some(4),
482 9 if is(b"max-stale") => Some(1),
483 9 if is(b"min-fresh") => Some(2),
484 12 if is(b"no-transform") => Some(5),
485 14 if is(b"only-if-cached") => Some(6),
486 14 if is(b"stale-if-error") => Some(7),
487 _ => None,
488 }
489}
490
491#[derive(Default)]
493struct RequestBuilder {
494 r: RequestDirectives,
495 seen: [bool; 8],
496}
497
498impl RequestBuilder {
499 fn push(&mut self, d: Directive<'_>) {
500 let r = &mut self.r;
501 let Some(slot) = request_slot(d.name) else {
502 r.unknown = r.unknown.saturating_add(1);
503 return;
504 };
505 let Some(seen) = self.seen.get_mut(slot) else {
506 return;
507 };
508 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
509 match slot {
510 0 => number(&mut r.max_age, seen, dup, d.value, inv),
511 1 => {
512 if *seen {
513 *dup = true;
514 } else {
515 *seen = true;
516 r.max_stale = Some(match d.value {
517 None => None,
518 v => seconds(v, inv),
519 });
520 }
521 }
522 2 => number(&mut r.min_fresh, seen, dup, d.value, inv),
523 3 => flag(&mut r.no_cache, seen, dup, d.value, inv, false),
524 4 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
525 5 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
526 6 => flag(&mut r.only_if_cached, seen, dup, d.value, inv, false),
527 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
528 }
529 }
530}
531
532pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
548 let mut b = ResponseBuilder::default();
549 let _ = walk_value(value, &Limits::DEFAULT, |d| b.push(d))?;
550 Ok(b.r)
551}
552
553pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
568 let mut b = RequestBuilder::default();
569 let _ = walk_value(value, &Limits::DEFAULT, |d| b.push(d))?;
570 Ok(b.r)
571}
572
573pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
591where
592 I: IntoIterator<Item = &'a [u8]>,
593 I::IntoIter: Clone,
594{
595 let mut b = ResponseBuilder::default();
596 for (n, line) in lines.into_iter().enumerate() {
597 let _ = walk_value(line, &Limits::DEFAULT, |d| b.push(d)).map_err(|e| e.on_line(n))?;
598 }
599 Ok(b.r)
600}
601
602pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
620where
621 I: IntoIterator<Item = &'a [u8]>,
622 I::IntoIter: Clone,
623{
624 let mut b = RequestBuilder::default();
625 for (n, line) in lines.into_iter().enumerate() {
626 let _ = walk_value(line, &Limits::DEFAULT, |d| b.push(d)).map_err(|e| e.on_line(n))?;
627 }
628 Ok(b.r)
629}
630
631pub struct Writer<'s, S: Sink> {
648 out: &'s mut S,
649 first: bool,
650}
651
652impl<'s, S: Sink> Writer<'s, S> {
653 pub fn new(out: &'s mut S) -> Self {
668 Self { out, first: true }
669 }
670
671 fn separator(&self) -> &'static [u8] {
672 if self.first { b"" } else { b", " }
673 }
674
675 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
695 if !is_token(name.as_bytes()) {
696 return Err(WriteError::NotToken);
697 }
698 let sep = self.separator();
699 reserve(self.out, sep.len() + name.len())?;
700 self.out.put(sep)?;
701 self.out.put(name.as_bytes())?;
702 self.first = false;
703 Ok(self)
704 }
705
706 pub fn seconds(
724 &mut self,
725 name: &str,
726 secs: u32,
727 ) -> core::result::Result<&mut Self, WriteError> {
728 if !is_token(name.as_bytes()) {
729 return Err(WriteError::NotToken);
730 }
731 let mut digits = crate::write::CountSink::new();
732 write_decimal_u64(u64::from(secs), &mut digits)?;
733 let sep = self.separator();
734 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
735 self.out.put(sep)?;
736 self.out.put(name.as_bytes())?;
737 self.out.put(b"=")?;
738 write_decimal_u64(u64::from(secs), self.out)?;
739 self.first = false;
740 Ok(self)
741 }
742
743 pub fn fields(
761 &mut self,
762 name: &str,
763 fields: &[&str],
764 ) -> core::result::Result<&mut Self, WriteError> {
765 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
766 return Err(WriteError::NotToken);
767 }
768 let sep = self.separator();
769 let list_len: usize =
770 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
771 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
772 self.out.put(sep)?;
773 self.out.put(name.as_bytes())?;
774 self.out.put(b"=\"")?;
775 for (i, f) in fields.iter().enumerate() {
776 if i > 0 {
777 self.out.put(b", ")?;
778 }
779 self.out.put(f.as_bytes())?;
780 }
781 self.out.put(b"\"")?;
782 self.first = false;
783 Ok(self)
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 #[test]
792 fn test_response_directives_cases() {
793 let r = |v: &[u8]| response_cache_control(v).unwrap();
794 assert_eq!(r(b"max-age=0").max_age, Some(0));
795 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
796 let bad = r(b"max-age=60s");
797 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
798 assert_eq!(
799 r(b"max-age=99999999999999999999").max_age,
800 Some(2_147_483_648)
801 );
802 let dup = r(b"max-age=1, max-age=2");
803 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
804 let nc = r(b"no-cache=\"Set-Cookie\"");
805 assert!(nc.no_cache && !nc.invalid_values);
806 let both = r(b"private, public");
807 assert!(both.private && both.public);
808 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
809 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
810 assert_eq!(
811 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
812 Some(60)
813 );
814 assert!(r(b"no-store=1").invalid_values);
815 assert!(r(b"max-age").invalid_values);
816 assert_eq!(r(b"").unknown, 0);
817 }
818
819 #[test]
820 fn test_request_directives_cases() {
821 let q = |v: &[u8]| request_cache_control(v).unwrap();
822 assert_eq!(q(b"max-stale").max_stale, Some(None));
823 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
824 assert!(q(b"only-if-cached").only_if_cached);
825 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
826 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
827 }
828
829 #[test]
830 fn test_cache_structure_errors() {
831 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
832 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
833 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
834 assert_eq!(k(b"=5"), ErrorKind::NotToken);
835 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
836 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
837 }
838
839 #[test]
840 fn test_lines() {
841 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
842 let r = response_lines(lines).unwrap();
843 assert!(r.public && r.max_age == Some(5));
844 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
845 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
846 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
847 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
848 }
849
850 #[test]
851 fn test_writer_round_trip_and_refusals() {
852 let mut buf = [0u8; 128];
853 let mut out = crate::SliceSink::new(&mut buf);
854 Writer::new(&mut out)
855 .flag("public")
856 .and_then(|w| w.seconds("max-age", u32::MAX))
857 .and_then(|w| w.fields("private", &["X-A"]))
858 .map(|_| ())
859 .unwrap();
860 let r = response_cache_control(out.written()).unwrap();
861 assert!(r.public && r.private);
862 assert_eq!(r.max_age, Some(2_147_483_648));
863 let mut w = Writer::new(&mut out);
864 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
865 assert!(matches!(
866 w.fields("no-cache", &["a b"]),
867 Err(WriteError::NotToken)
868 ));
869 let mut small = [0u8; 3];
870 let mut out = crate::SliceSink::new(&mut small);
871 assert!(matches!(
872 Writer::new(&mut out).flag("public"),
873 Err(WriteError::Full)
874 ));
875 assert!(out.is_empty());
876 }
877}