1use crate::{
20 class,
21 error::{ErrorKind, ParseError, Result, WriteError},
22 limits::Limits,
23 list,
24 num::parse_u32_saturating,
25 scan::control_kind,
26 text::{Word, is_token, parse_word},
27 write::{Sink, reserve, write_decimal_u64},
28};
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43pub struct Directive<'a> {
44 pub name: &'a [u8],
46 pub value: Option<Word<'a>>,
48}
49
50fn directive(e: &[u8]) -> Result<Directive<'_>> {
52 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
53 if n == 0 {
54 return Err(bad(e, 0));
55 }
56 let name = e.get(..n).unwrap_or(&[]);
57 match e.get(n) {
58 None => Ok(Directive { name, value: None }),
59 Some(b'=') => {
60 let rest = e.get(n + 1..).unwrap_or(&[]);
61 let (w, used) = parse_word(rest).map_err(|x| match x.kind() {
62 ErrorKind::Empty => bad(e, n + 1),
63 _ => x.shifted(n + 1),
64 })?;
65 if n + 1 + used != e.len() {
66 return Err(bad(e, n + 1 + used));
67 }
68 Ok(Directive {
69 name,
70 value: Some(w),
71 })
72 }
73 Some(_) => Err(bad(e, n)),
74 }
75}
76
77fn bad(e: &[u8], at: usize) -> ParseError {
78 match e.get(at) {
79 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
80 _ => ParseError::new(ErrorKind::NotToken, at),
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
97pub struct Directives<'a> {
98 list: list::List<'a>,
99}
100
101impl<'a> Directives<'a> {
102 pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
115 self.list.iter().filter_map(|e| directive(e).ok())
116 }
117
118 #[must_use]
129 pub const fn len(&self) -> usize {
130 self.list.len()
131 }
132
133 #[must_use]
145 pub const fn is_empty(&self) -> bool {
146 self.list.is_empty()
147 }
148}
149
150pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
169 let l = list::parse(value, limits)?;
170 for e in l.iter() {
171 let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
172 directive(e).map(|_| ()).map_err(|x| x.shifted(at))?;
173 }
174 Ok(Directives { list: l })
175}
176
177#[non_exhaustive]
196#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
197pub struct ResponseDirectives {
198 pub max_age: Option<u32>,
200 pub s_maxage: Option<u32>,
202 pub no_cache: bool,
204 pub no_store: bool,
206 pub private: bool,
208 pub public: bool,
210 pub must_revalidate: bool,
212 pub proxy_revalidate: bool,
214 pub must_understand: bool,
216 pub no_transform: bool,
218 pub immutable: bool,
220 pub stale_while_revalidate: Option<u32>,
222 pub stale_if_error: Option<u32>,
224 pub duplicates: bool,
226 pub invalid_values: bool,
228 pub unknown: u16,
230}
231
232#[non_exhaustive]
243#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
244pub struct RequestDirectives {
245 pub max_age: Option<u32>,
247 pub max_stale: Option<Option<u32>>,
249 pub min_fresh: Option<u32>,
251 pub no_cache: bool,
253 pub no_store: bool,
255 pub no_transform: bool,
257 pub only_if_cached: bool,
259 pub stale_if_error: Option<u32>,
261 pub duplicates: bool,
263 pub invalid_values: bool,
265 pub unknown: u16,
267}
268
269fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
272 let mut buf = [0u8; 32];
273 let digits = match value {
274 Some(Word::Token(t)) => t,
275 Some(q @ Word::Quoted(_)) => match q.unescape_into(&mut buf) {
276 Ok(d) => d,
277 Err(_) => {
278 *invalid = true;
279 return None;
280 }
281 },
282 None => {
283 *invalid = true;
284 return None;
285 }
286 };
287 match parse_u32_saturating(digits) {
288 Ok(n) => Some(n),
289 Err(_) => {
290 *invalid = true;
291 None
292 }
293 }
294}
295
296fn flag(
299 slot: &mut bool,
300 seen: &mut bool,
301 dup: &mut bool,
302 value: Option<Word<'_>>,
303 invalid: &mut bool,
304 list_ok: bool,
305) {
306 if *seen {
307 *dup = true;
308 return;
309 }
310 *seen = true;
311 *slot = true;
312 if value.is_some() && !list_ok {
313 *invalid = true;
314 }
315}
316
317fn number(
319 slot: &mut Option<u32>,
320 seen: &mut bool,
321 dup: &mut bool,
322 value: Option<Word<'_>>,
323 invalid: &mut bool,
324) {
325 if *seen {
326 *dup = true;
327 return;
328 }
329 *seen = true;
330 *slot = seconds(value, invalid);
331}
332
333fn response_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> ResponseDirectives {
334 let mut r = ResponseDirectives::default();
335 let mut seen = [false; 13];
336 for d in directives {
337 let n = d.name;
338 let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
339 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
340 match () {
341 () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
342 () if is(b"s-maxage") => number(&mut r.s_maxage, &mut seen[1], dup, d.value, inv),
343 () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[2], dup, d.value, inv, true),
344 () if is(b"no-store") => flag(&mut r.no_store, &mut seen[3], dup, d.value, inv, false),
345 () if is(b"private") => flag(&mut r.private, &mut seen[4], dup, d.value, inv, true),
346 () if is(b"public") => flag(&mut r.public, &mut seen[5], dup, d.value, inv, false),
347 () if is(b"must-revalidate") => flag(
348 &mut r.must_revalidate,
349 &mut seen[6],
350 dup,
351 d.value,
352 inv,
353 false,
354 ),
355 () if is(b"proxy-revalidate") => flag(
356 &mut r.proxy_revalidate,
357 &mut seen[7],
358 dup,
359 d.value,
360 inv,
361 false,
362 ),
363 () if is(b"must-understand") => flag(
364 &mut r.must_understand,
365 &mut seen[8],
366 dup,
367 d.value,
368 inv,
369 false,
370 ),
371 () if is(b"no-transform") => {
372 flag(&mut r.no_transform, &mut seen[9], dup, d.value, inv, false)
373 }
374 () if is(b"immutable") => {
375 flag(&mut r.immutable, &mut seen[10], dup, d.value, inv, false)
376 }
377 () if is(b"stale-while-revalidate") => {
378 number(
379 &mut r.stale_while_revalidate,
380 &mut seen[11],
381 dup,
382 d.value,
383 inv,
384 );
385 }
386 () if is(b"stale-if-error") => {
387 number(&mut r.stale_if_error, &mut seen[12], dup, d.value, inv)
388 }
389 () => r.unknown = r.unknown.saturating_add(1),
390 }
391 }
392 r
393}
394
395fn request_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> RequestDirectives {
396 let mut r = RequestDirectives::default();
397 let mut seen = [false; 8];
398 for d in directives {
399 let n = d.name;
400 let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
401 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
402 match () {
403 () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
404 () if is(b"max-stale") => {
405 if seen[1] {
406 *dup = true;
407 } else {
408 seen[1] = true;
409 r.max_stale = Some(match d.value {
410 None => None,
411 v => seconds(v, inv),
412 });
413 }
414 }
415 () if is(b"min-fresh") => number(&mut r.min_fresh, &mut seen[2], dup, d.value, inv),
416 () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[3], dup, d.value, inv, false),
417 () if is(b"no-store") => flag(&mut r.no_store, &mut seen[4], dup, d.value, inv, false),
418 () if is(b"no-transform") => {
419 flag(&mut r.no_transform, &mut seen[5], dup, d.value, inv, false)
420 }
421 () if is(b"only-if-cached") => flag(
422 &mut r.only_if_cached,
423 &mut seen[6],
424 dup,
425 d.value,
426 inv,
427 false,
428 ),
429 () if is(b"stale-if-error") => {
430 number(&mut r.stale_if_error, &mut seen[7], dup, d.value, inv)
431 }
432 () => r.unknown = r.unknown.saturating_add(1),
433 }
434 }
435 r
436}
437
438pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
454 Ok(response_from(parse(value, &Limits::DEFAULT)?.iter()))
455}
456
457pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
472 Ok(request_from(parse(value, &Limits::DEFAULT)?.iter()))
473}
474
475pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
493where
494 I: IntoIterator<Item = &'a [u8]>,
495 I::IntoIter: Clone,
496{
497 let lines = lines.into_iter();
498 for (n, line) in lines.clone().enumerate() {
499 parse(line, &Limits::DEFAULT)
500 .map(|_| ())
501 .map_err(|e| e.on_line(n))?;
502 }
503 Ok(response_from(lines.flat_map(|l| {
504 list::parse(l, &Limits::DEFAULT)
505 .into_iter()
506 .flat_map(|x| x.iter())
507 .filter_map(|e| directive(e).ok())
508 })))
509}
510
511pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
529where
530 I: IntoIterator<Item = &'a [u8]>,
531 I::IntoIter: Clone,
532{
533 let lines = lines.into_iter();
534 for (n, line) in lines.clone().enumerate() {
535 parse(line, &Limits::DEFAULT)
536 .map(|_| ())
537 .map_err(|e| e.on_line(n))?;
538 }
539 Ok(request_from(lines.flat_map(|l| {
540 list::parse(l, &Limits::DEFAULT)
541 .into_iter()
542 .flat_map(|x| x.iter())
543 .filter_map(|e| directive(e).ok())
544 })))
545}
546
547pub struct Writer<'s, S: Sink> {
564 out: &'s mut S,
565 first: bool,
566}
567
568impl<'s, S: Sink> Writer<'s, S> {
569 pub fn new(out: &'s mut S) -> Self {
584 Self { out, first: true }
585 }
586
587 fn separator(&self) -> &'static [u8] {
588 if self.first { b"" } else { b", " }
589 }
590
591 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
611 if !is_token(name.as_bytes()) {
612 return Err(WriteError::NotToken);
613 }
614 let sep = self.separator();
615 reserve(self.out, sep.len() + name.len())?;
616 self.out.put(sep)?;
617 self.out.put(name.as_bytes())?;
618 self.first = false;
619 Ok(self)
620 }
621
622 pub fn seconds(
640 &mut self,
641 name: &str,
642 secs: u32,
643 ) -> core::result::Result<&mut Self, WriteError> {
644 if !is_token(name.as_bytes()) {
645 return Err(WriteError::NotToken);
646 }
647 let mut digits = crate::write::CountSink::new();
648 write_decimal_u64(u64::from(secs), &mut digits)?;
649 let sep = self.separator();
650 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
651 self.out.put(sep)?;
652 self.out.put(name.as_bytes())?;
653 self.out.put(b"=")?;
654 write_decimal_u64(u64::from(secs), self.out)?;
655 self.first = false;
656 Ok(self)
657 }
658
659 pub fn fields(
677 &mut self,
678 name: &str,
679 fields: &[&str],
680 ) -> core::result::Result<&mut Self, WriteError> {
681 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
682 return Err(WriteError::NotToken);
683 }
684 let sep = self.separator();
685 let list_len: usize =
686 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
687 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
688 self.out.put(sep)?;
689 self.out.put(name.as_bytes())?;
690 self.out.put(b"=\"")?;
691 for (i, f) in fields.iter().enumerate() {
692 if i > 0 {
693 self.out.put(b", ")?;
694 }
695 self.out.put(f.as_bytes())?;
696 }
697 self.out.put(b"\"")?;
698 self.first = false;
699 Ok(self)
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn test_response_directives_cases() {
709 let r = |v: &[u8]| response_cache_control(v).unwrap();
710 assert_eq!(r(b"max-age=0").max_age, Some(0));
711 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
712 let bad = r(b"max-age=60s");
713 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
714 assert_eq!(
715 r(b"max-age=99999999999999999999").max_age,
716 Some(2_147_483_648)
717 );
718 let dup = r(b"max-age=1, max-age=2");
719 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
720 let nc = r(b"no-cache=\"Set-Cookie\"");
721 assert!(nc.no_cache && !nc.invalid_values);
722 let both = r(b"private, public");
723 assert!(both.private && both.public);
724 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
725 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
726 assert_eq!(
727 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
728 Some(60)
729 );
730 assert!(r(b"no-store=1").invalid_values);
731 assert!(r(b"max-age").invalid_values);
732 assert_eq!(r(b"").unknown, 0);
733 }
734
735 #[test]
736 fn test_request_directives_cases() {
737 let q = |v: &[u8]| request_cache_control(v).unwrap();
738 assert_eq!(q(b"max-stale").max_stale, Some(None));
739 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
740 assert!(q(b"only-if-cached").only_if_cached);
741 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
742 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
743 }
744
745 #[test]
746 fn test_cache_structure_errors() {
747 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
748 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
749 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
750 assert_eq!(k(b"=5"), ErrorKind::NotToken);
751 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
752 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
753 }
754
755 #[test]
756 fn test_lines() {
757 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
758 let r = response_lines(lines).unwrap();
759 assert!(r.public && r.max_age == Some(5));
760 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
761 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
762 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
763 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
764 }
765
766 #[test]
767 fn test_writer_round_trip_and_refusals() {
768 let mut buf = [0u8; 128];
769 let mut out = crate::SliceSink::new(&mut buf);
770 Writer::new(&mut out)
771 .flag("public")
772 .and_then(|w| w.seconds("max-age", u32::MAX))
773 .and_then(|w| w.fields("private", &["X-A"]))
774 .map(|_| ())
775 .unwrap();
776 let r = response_cache_control(out.written()).unwrap();
777 assert!(r.public && r.private);
778 assert_eq!(r.max_age, Some(2_147_483_648));
779 let mut w = Writer::new(&mut out);
780 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
781 assert!(matches!(
782 w.fields("no-cache", &["a b"]),
783 Err(WriteError::NotToken)
784 ));
785 let mut small = [0u8; 3];
786 let mut out = crate::SliceSink::new(&mut small);
787 assert!(matches!(
788 Writer::new(&mut out).flag("public"),
789 Err(WriteError::Full)
790 ));
791 assert!(out.is_empty());
792 }
793}