1use crate::{
20 class,
21 error::{ErrorKind, ParseError, Result, WriteError},
22 limits::Limits,
23 num::parse_u32_saturating,
24 scan::control_kind,
25 text::{Word, is_token, parse_word, skip_ows, trim_ows},
26 write::{Sink, reserve, write_decimal_u64},
27};
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
42pub struct Directive<'a> {
43 pub name: &'a [u8],
45 pub value: Option<Word<'a>>,
47}
48
49#[inline]
51fn skip_ws(mut s: &[u8]) -> &[u8] {
52 while let [b' ' | b'\t', rest @ ..] = s {
53 s = rest;
54 }
55 s
56}
57
58fn directive(s: &[u8]) -> Result<(Directive<'_>, &[u8])> {
61 let n = s
62 .iter()
63 .position(|&b| !class::is_tchar(b))
64 .unwrap_or(s.len());
65 if n == 0 {
66 return Err(bad(s, 0));
67 }
68 let (name, after) = s.split_at(n);
69 let [b'=', value @ ..] = after else {
70 return Ok((Directive { name, value: None }, after));
71 };
72 let (w, used) = parse_word(value).map_err(|x| match x.kind() {
73 ErrorKind::Empty => bad(s, n + 1),
74 _ => x.shifted(n + 1),
75 })?;
76 Ok((
77 Directive {
78 name,
79 value: Some(w),
80 },
81 value.get(used..).unwrap_or(&[]),
82 ))
83}
84
85fn walk<'a>(v: &'a [u8], limits: &Limits, mut f: impl FnMut(Directive<'a>)) -> Result<usize> {
90 let at = |rest: &[u8]| v.len() - rest.len();
91 let (mut rest, mut count, mut empty) = (v, 0usize, 0usize);
92 loop {
93 rest = skip_ws(rest);
94 match rest {
95 [] => return Ok(count),
96 [b',', tail @ ..] => {
97 empty += 1;
98 if empty > usize::from(limits.empty_elements) {
99 return Err(ParseError::new(ErrorKind::TooManyEmptyElements, at(rest)));
100 }
101 rest = tail;
102 continue;
103 }
104 _ => {}
105 }
106 let (d, tail) = directive(rest).map_err(|e| e.shifted(at(rest)))?;
107 count += 1;
108 if count > usize::from(limits.list_members) {
109 return Err(ParseError::new(ErrorKind::TooManyMembers, at(rest)));
110 }
111 f(d);
112 rest = skip_ws(tail);
113 match rest {
114 [] => return Ok(count),
115 [b',', tail @ ..] => rest = tail,
116 _ => return Err(bad(rest, 0).shifted(at(rest))),
117 }
118 }
119}
120
121fn walk_value<'a>(
123 value: &'a [u8],
124 limits: &Limits,
125 f: impl FnMut(Directive<'a>),
126) -> Result<(&'a [u8], usize)> {
127 let lead = skip_ows(value);
128 let v = trim_ows(value);
129 let count = walk(v, limits, f).map_err(|e| e.shifted(lead))?;
130 Ok((v, count))
131}
132
133fn bad(e: &[u8], at: usize) -> ParseError {
134 match e.get(at) {
135 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
136 _ => ParseError::new(ErrorKind::NotToken, at),
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub struct Directives<'a> {
154 text: &'a [u8],
156 count: usize,
157}
158
159impl<'a> Directives<'a> {
160 pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
173 DirectiveIter { s: self.text }
174 }
175
176 #[must_use]
187 pub const fn len(&self) -> usize {
188 self.count
189 }
190
191 #[must_use]
203 pub const fn is_empty(&self) -> bool {
204 self.count == 0
205 }
206}
207
208struct DirectiveIter<'a> {
210 s: &'a [u8],
211}
212
213impl<'a> Iterator for DirectiveIter<'a> {
214 type Item = Directive<'a>;
215
216 fn next(&mut self) -> Option<Directive<'a>> {
217 let mut rest = self.s;
218 while let [b' ' | b'\t' | b',', tail @ ..] = rest {
219 rest = tail;
220 }
221 let (d, tail) = directive(rest).ok()?;
222 self.s = tail;
223 Some(d)
224 }
225}
226
227pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
246 let (text, count) = walk_value(value, limits, |_| {})?;
247 Ok(Directives { text, count })
248}
249
250#[non_exhaustive]
269#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
270pub struct ResponseDirectives {
271 pub max_age: Option<u32>,
273 pub s_maxage: Option<u32>,
275 pub no_cache: bool,
277 pub no_store: bool,
279 pub private: bool,
281 pub public: bool,
283 pub must_revalidate: bool,
285 pub proxy_revalidate: bool,
287 pub must_understand: bool,
289 pub no_transform: bool,
291 pub immutable: bool,
293 pub stale_while_revalidate: Option<u32>,
295 pub stale_if_error: Option<u32>,
297 pub duplicates: bool,
299 pub invalid_values: bool,
301 pub unknown: u16,
303}
304
305#[non_exhaustive]
316#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
317pub struct RequestDirectives {
318 pub max_age: Option<u32>,
320 pub max_stale: Option<Option<u32>>,
322 pub min_fresh: Option<u32>,
324 pub no_cache: bool,
326 pub no_store: bool,
328 pub no_transform: bool,
330 pub only_if_cached: bool,
332 pub stale_if_error: Option<u32>,
334 pub duplicates: bool,
336 pub invalid_values: bool,
338 pub unknown: u16,
340}
341
342fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
345 let n = match value {
349 Some(Word::Token(t)) => parse_u32_saturating(t).ok(),
350 Some(Word::Quoted(q)) => match q.as_plain() {
351 Some(p) => parse_u32_saturating(p).ok(),
352 None => {
353 let mut n: Option<u32> = None;
354 for b in q.bytes() {
355 if !b.is_ascii_digit() {
356 n = None;
357 break;
358 }
359 n = Some(
360 n.unwrap_or(0)
361 .saturating_mul(10)
362 .saturating_add(u32::from(b - b'0'))
363 .min(crate::num::DELTA_SECONDS_MAX),
364 );
365 }
366 n
367 }
368 },
369 None => None,
370 };
371 if n.is_none() {
372 *invalid = true;
373 }
374 n
375}
376
377fn flag(
380 slot: &mut bool,
381 seen: &mut bool,
382 dup: &mut bool,
383 value: Option<Word<'_>>,
384 invalid: &mut bool,
385 list_ok: bool,
386) {
387 if *seen {
388 *dup = true;
389 return;
390 }
391 *seen = true;
392 *slot = true;
393 if value.is_some() && !list_ok {
394 *invalid = true;
395 }
396}
397
398fn number(
400 slot: &mut Option<u32>,
401 seen: &mut bool,
402 dup: &mut bool,
403 value: Option<Word<'_>>,
404 invalid: &mut bool,
405) {
406 if *seen {
407 *dup = true;
408 return;
409 }
410 *seen = true;
411 *slot = seconds(value, invalid);
412}
413
414fn is_name(n: &[u8], lower: &[u8]) -> bool {
419 n.len() == lower.len() && n.iter().zip(lower).all(|(&b, &p)| b | 0x20 == p)
420}
421
422fn response_slot(n: &[u8]) -> Option<usize> {
424 let is = |s: &[u8]| is_name(n, s);
425 match n.len() {
426 6 if is(b"public") => Some(5),
427 7 if is(b"max-age") => Some(0),
428 7 if is(b"private") => Some(4),
429 8 if is(b"s-maxage") => Some(1),
430 8 if is(b"no-cache") => Some(2),
431 8 if is(b"no-store") => Some(3),
432 9 if is(b"immutable") => Some(10),
433 12 if is(b"no-transform") => Some(9),
434 14 if is(b"stale-if-error") => Some(12),
435 15 if is(b"must-revalidate") => Some(6),
436 15 if is(b"must-understand") => Some(8),
437 16 if is(b"proxy-revalidate") => Some(7),
438 22 if is(b"stale-while-revalidate") => Some(11),
439 _ => None,
440 }
441}
442
443#[derive(Default)]
445struct ResponseBuilder {
446 r: ResponseDirectives,
447 seen: [bool; 13],
448}
449
450impl ResponseBuilder {
451 fn push(&mut self, d: Directive<'_>) {
452 let r = &mut self.r;
453 let Some(slot) = response_slot(d.name) else {
454 r.unknown = r.unknown.saturating_add(1);
455 return;
456 };
457 let Some(seen) = self.seen.get_mut(slot) else {
458 return;
459 };
460 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
461 match slot {
462 0 => number(&mut r.max_age, seen, dup, d.value, inv),
463 1 => number(&mut r.s_maxage, seen, dup, d.value, inv),
464 2 => flag(&mut r.no_cache, seen, dup, d.value, inv, true),
465 3 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
466 4 => flag(&mut r.private, seen, dup, d.value, inv, true),
467 5 => flag(&mut r.public, seen, dup, d.value, inv, false),
468 6 => flag(&mut r.must_revalidate, seen, dup, d.value, inv, false),
469 7 => flag(&mut r.proxy_revalidate, seen, dup, d.value, inv, false),
470 8 => flag(&mut r.must_understand, seen, dup, d.value, inv, false),
471 9 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
472 10 => flag(&mut r.immutable, seen, dup, d.value, inv, false),
473 11 => number(&mut r.stale_while_revalidate, seen, dup, d.value, inv),
474 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
475 }
476 }
477}
478
479fn request_slot(n: &[u8]) -> Option<usize> {
481 let is = |s: &[u8]| is_name(n, s);
482 match n.len() {
483 7 if is(b"max-age") => Some(0),
484 8 if is(b"no-cache") => Some(3),
485 8 if is(b"no-store") => Some(4),
486 9 if is(b"max-stale") => Some(1),
487 9 if is(b"min-fresh") => Some(2),
488 12 if is(b"no-transform") => Some(5),
489 14 if is(b"only-if-cached") => Some(6),
490 14 if is(b"stale-if-error") => Some(7),
491 _ => None,
492 }
493}
494
495#[derive(Default)]
497struct RequestBuilder {
498 r: RequestDirectives,
499 seen: [bool; 8],
500}
501
502impl RequestBuilder {
503 fn push(&mut self, d: Directive<'_>) {
504 let r = &mut self.r;
505 let Some(slot) = request_slot(d.name) else {
506 r.unknown = r.unknown.saturating_add(1);
507 return;
508 };
509 let Some(seen) = self.seen.get_mut(slot) else {
510 return;
511 };
512 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
513 match slot {
514 0 => number(&mut r.max_age, seen, dup, d.value, inv),
515 1 => {
516 if *seen {
517 *dup = true;
518 } else {
519 *seen = true;
520 r.max_stale = Some(match d.value {
521 None => None,
522 v => seconds(v, inv),
523 });
524 }
525 }
526 2 => number(&mut r.min_fresh, seen, dup, d.value, inv),
527 3 => flag(&mut r.no_cache, seen, dup, d.value, inv, false),
528 4 => flag(&mut r.no_store, seen, dup, d.value, inv, false),
529 5 => flag(&mut r.no_transform, seen, dup, d.value, inv, false),
530 6 => flag(&mut r.only_if_cached, seen, dup, d.value, inv, false),
531 _ => number(&mut r.stale_if_error, seen, dup, d.value, inv),
532 }
533 }
534}
535
536pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
552 let mut b = ResponseBuilder::default();
553 let _ = walk_value(value, &Limits::DEFAULT, |d| b.push(d))?;
554 Ok(b.r)
555}
556
557pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
572 let mut b = RequestBuilder::default();
573 let _ = walk_value(value, &Limits::DEFAULT, |d| b.push(d))?;
574 Ok(b.r)
575}
576
577pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
595where
596 I: IntoIterator<Item = &'a [u8]>,
597 I::IntoIter: Clone,
598{
599 let mut b = ResponseBuilder::default();
600 for (n, line) in lines.into_iter().enumerate() {
601 let _ = walk_value(line, &Limits::DEFAULT, |d| b.push(d)).map_err(|e| e.on_line(n))?;
602 }
603 Ok(b.r)
604}
605
606pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
624where
625 I: IntoIterator<Item = &'a [u8]>,
626 I::IntoIter: Clone,
627{
628 let mut b = RequestBuilder::default();
629 for (n, line) in lines.into_iter().enumerate() {
630 let _ = walk_value(line, &Limits::DEFAULT, |d| b.push(d)).map_err(|e| e.on_line(n))?;
631 }
632 Ok(b.r)
633}
634
635pub struct Writer<'s, S: Sink> {
652 out: &'s mut S,
653 first: bool,
654}
655
656impl<'s, S: Sink> Writer<'s, S> {
657 pub fn new(out: &'s mut S) -> Self {
672 Self { out, first: true }
673 }
674
675 fn separator(&self) -> &'static [u8] {
676 if self.first { b"" } else { b", " }
677 }
678
679 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
699 if !is_token(name.as_bytes()) {
700 return Err(WriteError::NotToken);
701 }
702 let sep = self.separator();
703 reserve(self.out, sep.len() + name.len())?;
704 self.out.put(sep)?;
705 self.out.put(name.as_bytes())?;
706 self.first = false;
707 Ok(self)
708 }
709
710 pub fn seconds(
728 &mut self,
729 name: &str,
730 secs: u32,
731 ) -> core::result::Result<&mut Self, WriteError> {
732 if !is_token(name.as_bytes()) {
733 return Err(WriteError::NotToken);
734 }
735 let mut digits = crate::write::CountSink::new();
736 write_decimal_u64(u64::from(secs), &mut digits)?;
737 let sep = self.separator();
738 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
739 self.out.put(sep)?;
740 self.out.put(name.as_bytes())?;
741 self.out.put(b"=")?;
742 write_decimal_u64(u64::from(secs), self.out)?;
743 self.first = false;
744 Ok(self)
745 }
746
747 pub fn fields(
765 &mut self,
766 name: &str,
767 fields: &[&str],
768 ) -> core::result::Result<&mut Self, WriteError> {
769 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
770 return Err(WriteError::NotToken);
771 }
772 let sep = self.separator();
773 let list_len: usize =
774 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
775 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
776 self.out.put(sep)?;
777 self.out.put(name.as_bytes())?;
778 self.out.put(b"=\"")?;
779 for (i, f) in fields.iter().enumerate() {
780 if i > 0 {
781 self.out.put(b", ")?;
782 }
783 self.out.put(f.as_bytes())?;
784 }
785 self.out.put(b"\"")?;
786 self.first = false;
787 Ok(self)
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 #[test]
796 fn test_response_directives_cases() {
797 let r = |v: &[u8]| response_cache_control(v).unwrap();
798 assert_eq!(r(b"max-age=0").max_age, Some(0));
799 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
800 let bad = r(b"max-age=60s");
801 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
802 assert_eq!(
803 r(b"max-age=99999999999999999999").max_age,
804 Some(2_147_483_648)
805 );
806 let dup = r(b"max-age=1, max-age=2");
807 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
808 let nc = r(b"no-cache=\"Set-Cookie\"");
809 assert!(nc.no_cache && !nc.invalid_values);
810 let both = r(b"private, public");
811 assert!(both.private && both.public);
812 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
813 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
814 assert_eq!(
815 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
816 Some(60)
817 );
818 assert!(r(b"no-store=1").invalid_values);
819 assert!(r(b"max-age").invalid_values);
820 assert_eq!(r(b"").unknown, 0);
821 }
822
823 #[test]
824 fn test_request_directives_cases() {
825 let q = |v: &[u8]| request_cache_control(v).unwrap();
826 assert_eq!(q(b"max-stale").max_stale, Some(None));
827 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
828 assert!(q(b"only-if-cached").only_if_cached);
829 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
830 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
831 }
832
833 #[test]
834 fn test_cache_structure_errors() {
835 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
836 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
837 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
838 assert_eq!(k(b"=5"), ErrorKind::NotToken);
839 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
840 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
841 }
842
843 #[test]
844 fn test_lines() {
845 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
846 let r = response_lines(lines).unwrap();
847 assert!(r.public && r.max_age == Some(5));
848 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
849 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
850 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
851 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
852 }
853
854 #[test]
855 fn test_writer_round_trip_and_refusals() {
856 let mut buf = [0u8; 128];
857 let mut out = crate::SliceSink::new(&mut buf);
858 Writer::new(&mut out)
859 .flag("public")
860 .and_then(|w| w.seconds("max-age", u32::MAX))
861 .and_then(|w| w.fields("private", &["X-A"]))
862 .map(|_| ())
863 .unwrap();
864 let r = response_cache_control(out.written()).unwrap();
865 assert!(r.public && r.private);
866 assert_eq!(r.max_age, Some(2_147_483_648));
867 let mut w = Writer::new(&mut out);
868 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
869 assert!(matches!(
870 w.fields("no-cache", &["a b"]),
871 Err(WriteError::NotToken)
872 ));
873 let mut small = [0u8; 3];
874 let mut out = crate::SliceSink::new(&mut small);
875 assert!(matches!(
876 Writer::new(&mut out).flag("public"),
877 Err(WriteError::Full)
878 ));
879 assert!(out.is_empty());
880 }
881}