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)]
32pub struct Directive<'a> {
33 pub name: &'a [u8],
35 pub value: Option<Word<'a>>,
37}
38
39fn directive(e: &[u8]) -> Result<Directive<'_>> {
41 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
42 if n == 0 {
43 return Err(bad(e, 0));
44 }
45 let name = e.get(..n).unwrap_or(&[]);
46 match e.get(n) {
47 None => Ok(Directive { name, value: None }),
48 Some(b'=') => {
49 let rest = e.get(n + 1..).unwrap_or(&[]);
50 let (w, used) = parse_word(rest).map_err(|x| match x.kind() {
51 ErrorKind::Empty => bad(e, n + 1),
52 _ => x.shifted(n + 1),
53 })?;
54 if n + 1 + used != e.len() {
55 return Err(bad(e, n + 1 + used));
56 }
57 Ok(Directive {
58 name,
59 value: Some(w),
60 })
61 }
62 Some(_) => Err(bad(e, n)),
63 }
64}
65
66fn bad(e: &[u8], at: usize) -> ParseError {
67 match e.get(at) {
68 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
69 _ => ParseError::new(ErrorKind::NotToken, at),
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub struct Directives<'a> {
76 list: list::List<'a>,
77}
78
79impl<'a> Directives<'a> {
80 pub fn iter(&self) -> impl Iterator<Item = Directive<'a>> + use<'a> {
82 self.list.iter().filter_map(|e| directive(e).ok())
83 }
84
85 #[must_use]
87 pub const fn len(&self) -> usize {
88 self.list.len()
89 }
90
91 #[must_use]
93 pub const fn is_empty(&self) -> bool {
94 self.list.is_empty()
95 }
96}
97
98pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<Directives<'a>> {
117 let l = list::parse(value, limits)?;
118 for e in l.iter() {
119 let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
120 directive(e).map(|_| ()).map_err(|x| x.shifted(at))?;
121 }
122 Ok(Directives { list: l })
123}
124
125#[non_exhaustive]
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
135pub struct ResponseDirectives {
136 pub max_age: Option<u32>,
138 pub s_maxage: Option<u32>,
140 pub no_cache: bool,
142 pub no_store: bool,
144 pub private: bool,
146 pub public: bool,
148 pub must_revalidate: bool,
150 pub proxy_revalidate: bool,
152 pub must_understand: bool,
154 pub no_transform: bool,
156 pub immutable: bool,
158 pub stale_while_revalidate: Option<u32>,
160 pub stale_if_error: Option<u32>,
162 pub duplicates: bool,
164 pub invalid_values: bool,
166 pub unknown: u16,
168}
169
170#[non_exhaustive]
172#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
173pub struct RequestDirectives {
174 pub max_age: Option<u32>,
176 pub max_stale: Option<Option<u32>>,
178 pub min_fresh: Option<u32>,
180 pub no_cache: bool,
182 pub no_store: bool,
184 pub no_transform: bool,
186 pub only_if_cached: bool,
188 pub stale_if_error: Option<u32>,
190 pub duplicates: bool,
192 pub invalid_values: bool,
194 pub unknown: u16,
196}
197
198fn seconds(value: Option<Word<'_>>, invalid: &mut bool) -> Option<u32> {
201 let mut buf = [0u8; 32];
202 let digits = match value {
203 Some(Word::Token(t)) => t,
204 Some(q @ Word::Quoted(_)) => match q.unescape_into(&mut buf) {
205 Ok(d) => d,
206 Err(_) => {
207 *invalid = true;
208 return None;
209 }
210 },
211 None => {
212 *invalid = true;
213 return None;
214 }
215 };
216 match parse_u32_saturating(digits) {
217 Ok(n) => Some(n),
218 Err(_) => {
219 *invalid = true;
220 None
221 }
222 }
223}
224
225fn flag(
228 slot: &mut bool,
229 seen: &mut bool,
230 dup: &mut bool,
231 value: Option<Word<'_>>,
232 invalid: &mut bool,
233 list_ok: bool,
234) {
235 if *seen {
236 *dup = true;
237 return;
238 }
239 *seen = true;
240 *slot = true;
241 if value.is_some() && !list_ok {
242 *invalid = true;
243 }
244}
245
246fn number(
248 slot: &mut Option<u32>,
249 seen: &mut bool,
250 dup: &mut bool,
251 value: Option<Word<'_>>,
252 invalid: &mut bool,
253) {
254 if *seen {
255 *dup = true;
256 return;
257 }
258 *seen = true;
259 *slot = seconds(value, invalid);
260}
261
262fn response_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> ResponseDirectives {
263 let mut r = ResponseDirectives::default();
264 let mut seen = [false; 13];
265 for d in directives {
266 let n = d.name;
267 let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
268 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
269 match () {
270 () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
271 () if is(b"s-maxage") => number(&mut r.s_maxage, &mut seen[1], dup, d.value, inv),
272 () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[2], dup, d.value, inv, true),
273 () if is(b"no-store") => flag(&mut r.no_store, &mut seen[3], dup, d.value, inv, false),
274 () if is(b"private") => flag(&mut r.private, &mut seen[4], dup, d.value, inv, true),
275 () if is(b"public") => flag(&mut r.public, &mut seen[5], dup, d.value, inv, false),
276 () if is(b"must-revalidate") => flag(
277 &mut r.must_revalidate,
278 &mut seen[6],
279 dup,
280 d.value,
281 inv,
282 false,
283 ),
284 () if is(b"proxy-revalidate") => flag(
285 &mut r.proxy_revalidate,
286 &mut seen[7],
287 dup,
288 d.value,
289 inv,
290 false,
291 ),
292 () if is(b"must-understand") => flag(
293 &mut r.must_understand,
294 &mut seen[8],
295 dup,
296 d.value,
297 inv,
298 false,
299 ),
300 () if is(b"no-transform") => {
301 flag(&mut r.no_transform, &mut seen[9], dup, d.value, inv, false)
302 }
303 () if is(b"immutable") => {
304 flag(&mut r.immutable, &mut seen[10], dup, d.value, inv, false)
305 }
306 () if is(b"stale-while-revalidate") => {
307 number(
308 &mut r.stale_while_revalidate,
309 &mut seen[11],
310 dup,
311 d.value,
312 inv,
313 );
314 }
315 () if is(b"stale-if-error") => {
316 number(&mut r.stale_if_error, &mut seen[12], dup, d.value, inv)
317 }
318 () => r.unknown = r.unknown.saturating_add(1),
319 }
320 }
321 r
322}
323
324fn request_from<'a>(directives: impl Iterator<Item = Directive<'a>>) -> RequestDirectives {
325 let mut r = RequestDirectives::default();
326 let mut seen = [false; 8];
327 for d in directives {
328 let n = d.name;
329 let is = |s: &[u8]| n.eq_ignore_ascii_case(s);
330 let (dup, inv) = (&mut r.duplicates, &mut r.invalid_values);
331 match () {
332 () if is(b"max-age") => number(&mut r.max_age, &mut seen[0], dup, d.value, inv),
333 () if is(b"max-stale") => {
334 if seen[1] {
335 *dup = true;
336 } else {
337 seen[1] = true;
338 r.max_stale = Some(match d.value {
339 None => None,
340 v => seconds(v, inv),
341 });
342 }
343 }
344 () if is(b"min-fresh") => number(&mut r.min_fresh, &mut seen[2], dup, d.value, inv),
345 () if is(b"no-cache") => flag(&mut r.no_cache, &mut seen[3], dup, d.value, inv, false),
346 () if is(b"no-store") => flag(&mut r.no_store, &mut seen[4], dup, d.value, inv, false),
347 () if is(b"no-transform") => {
348 flag(&mut r.no_transform, &mut seen[5], dup, d.value, inv, false)
349 }
350 () if is(b"only-if-cached") => flag(
351 &mut r.only_if_cached,
352 &mut seen[6],
353 dup,
354 d.value,
355 inv,
356 false,
357 ),
358 () if is(b"stale-if-error") => {
359 number(&mut r.stale_if_error, &mut seen[7], dup, d.value, inv)
360 }
361 () => r.unknown = r.unknown.saturating_add(1),
362 }
363 }
364 r
365}
366
367pub fn response_cache_control(value: &[u8]) -> Result<ResponseDirectives> {
383 Ok(response_from(parse(value, &Limits::DEFAULT)?.iter()))
384}
385
386pub fn request_cache_control(value: &[u8]) -> Result<RequestDirectives> {
401 Ok(request_from(parse(value, &Limits::DEFAULT)?.iter()))
402}
403
404pub fn response_lines<'a, I>(lines: I) -> Result<ResponseDirectives>
410where
411 I: IntoIterator<Item = &'a [u8]>,
412 I::IntoIter: Clone,
413{
414 let lines = lines.into_iter();
415 for (n, line) in lines.clone().enumerate() {
416 parse(line, &Limits::DEFAULT)
417 .map(|_| ())
418 .map_err(|e| e.on_line(n))?;
419 }
420 Ok(response_from(lines.flat_map(|l| {
421 list::parse(l, &Limits::DEFAULT)
422 .into_iter()
423 .flat_map(|x| x.iter())
424 .filter_map(|e| directive(e).ok())
425 })))
426}
427
428pub fn request_lines<'a, I>(lines: I) -> Result<RequestDirectives>
434where
435 I: IntoIterator<Item = &'a [u8]>,
436 I::IntoIter: Clone,
437{
438 let lines = lines.into_iter();
439 for (n, line) in lines.clone().enumerate() {
440 parse(line, &Limits::DEFAULT)
441 .map(|_| ())
442 .map_err(|e| e.on_line(n))?;
443 }
444 Ok(request_from(lines.flat_map(|l| {
445 list::parse(l, &Limits::DEFAULT)
446 .into_iter()
447 .flat_map(|x| x.iter())
448 .filter_map(|e| directive(e).ok())
449 })))
450}
451
452pub struct Writer<'s, S: Sink> {
457 out: &'s mut S,
458 first: bool,
459}
460
461impl<'s, S: Sink> Writer<'s, S> {
462 pub fn new(out: &'s mut S) -> Self {
464 Self { out, first: true }
465 }
466
467 fn separator(&self) -> &'static [u8] {
468 if self.first { b"" } else { b", " }
469 }
470
471 pub fn flag(&mut self, name: &str) -> core::result::Result<&mut Self, WriteError> {
478 if !is_token(name.as_bytes()) {
479 return Err(WriteError::NotToken);
480 }
481 let sep = self.separator();
482 reserve(self.out, sep.len() + name.len())?;
483 self.out.put(sep)?;
484 self.out.put(name.as_bytes())?;
485 self.first = false;
486 Ok(self)
487 }
488
489 pub fn seconds(
495 &mut self,
496 name: &str,
497 secs: u32,
498 ) -> core::result::Result<&mut Self, WriteError> {
499 if !is_token(name.as_bytes()) {
500 return Err(WriteError::NotToken);
501 }
502 let mut digits = crate::write::CountSink::new();
503 write_decimal_u64(u64::from(secs), &mut digits)?;
504 let sep = self.separator();
505 reserve(self.out, sep.len() + name.len() + 1 + digits.count())?;
506 self.out.put(sep)?;
507 self.out.put(name.as_bytes())?;
508 self.out.put(b"=")?;
509 write_decimal_u64(u64::from(secs), self.out)?;
510 self.first = false;
511 Ok(self)
512 }
513
514 pub fn fields(
532 &mut self,
533 name: &str,
534 fields: &[&str],
535 ) -> core::result::Result<&mut Self, WriteError> {
536 if !is_token(name.as_bytes()) || fields.iter().any(|f| !is_token(f.as_bytes())) {
537 return Err(WriteError::NotToken);
538 }
539 let sep = self.separator();
540 let list_len: usize =
541 fields.iter().map(|f| f.len()).sum::<usize>() + fields.len().saturating_sub(1) * 2;
542 reserve(self.out, sep.len() + name.len() + 3 + list_len)?;
543 self.out.put(sep)?;
544 self.out.put(name.as_bytes())?;
545 self.out.put(b"=\"")?;
546 for (i, f) in fields.iter().enumerate() {
547 if i > 0 {
548 self.out.put(b", ")?;
549 }
550 self.out.put(f.as_bytes())?;
551 }
552 self.out.put(b"\"")?;
553 self.first = false;
554 Ok(self)
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561
562 #[test]
563 fn test_response_directives_cases() {
564 let r = |v: &[u8]| response_cache_control(v).unwrap();
565 assert_eq!(r(b"max-age=0").max_age, Some(0));
566 assert_eq!(r(b"max-age=\"60\"").max_age, Some(60));
567 let bad = r(b"max-age=60s");
568 assert_eq!((bad.max_age, bad.invalid_values), (None, true));
569 assert_eq!(
570 r(b"max-age=99999999999999999999").max_age,
571 Some(2_147_483_648)
572 );
573 let dup = r(b"max-age=1, max-age=2");
574 assert_eq!((dup.max_age, dup.duplicates), (Some(1), true));
575 let nc = r(b"no-cache=\"Set-Cookie\"");
576 assert!(nc.no_cache && !nc.invalid_values);
577 let both = r(b"private, public");
578 assert!(both.private && both.public);
579 assert_eq!(r(b"x-unknown, y=1").unknown, 2);
580 assert!(r(b"no-store, , NO-TRANSFORM, immutable").no_transform);
581 assert_eq!(
582 r(b"stale-while-revalidate=30, stale-if-error=60").stale_if_error,
583 Some(60)
584 );
585 assert!(r(b"no-store=1").invalid_values);
586 assert!(r(b"max-age").invalid_values);
587 assert_eq!(r(b"").unknown, 0);
588 }
589
590 #[test]
591 fn test_request_directives_cases() {
592 let q = |v: &[u8]| request_cache_control(v).unwrap();
593 assert_eq!(q(b"max-stale").max_stale, Some(None));
594 assert_eq!(q(b"max-stale=10").max_stale, Some(Some(10)));
595 assert!(q(b"only-if-cached").only_if_cached);
596 assert_eq!(q(b"min-fresh=5, min-fresh=6").min_fresh, Some(5));
597 assert!(q(b"min-fresh=5, min-fresh=6").duplicates);
598 }
599
600 #[test]
601 fn test_cache_structure_errors() {
602 let k = |v: &[u8]| response_cache_control(v).unwrap_err().kind();
603 assert_eq!(k(b"max-age = 5"), ErrorKind::NotToken);
604 assert_eq!(k(b"max-age=\"5"), ErrorKind::UnterminatedQuote);
605 assert_eq!(k(b"=5"), ErrorKind::NotToken);
606 assert_eq!(k(b"a=b c"), ErrorKind::NotToken);
607 assert_eq!(k(b"a=\x01"), ErrorKind::Control);
608 }
609
610 #[test]
611 fn test_lines() {
612 let lines: [&[u8]; 2] = [b"public", b"max-age=5"];
613 let r = response_lines(lines).unwrap();
614 assert!(r.public && r.max_age == Some(5));
615 let bad: [&[u8]; 2] = [b"public", b"max-age=\"5"];
616 assert_eq!(response_lines(bad).unwrap_err().line(), 1);
617 let req: [&[u8]; 2] = [b"no-cache", b"max-stale"];
618 assert_eq!(request_lines(req).unwrap().max_stale, Some(None));
619 }
620
621 #[test]
622 fn test_writer_round_trip_and_refusals() {
623 let mut buf = [0u8; 128];
624 let mut out = crate::SliceSink::new(&mut buf);
625 Writer::new(&mut out)
626 .flag("public")
627 .and_then(|w| w.seconds("max-age", u32::MAX))
628 .and_then(|w| w.fields("private", &["X-A"]))
629 .map(|_| ())
630 .unwrap();
631 let r = response_cache_control(out.written()).unwrap();
632 assert!(r.public && r.private);
633 assert_eq!(r.max_age, Some(2_147_483_648));
634 let mut w = Writer::new(&mut out);
635 assert!(matches!(w.flag("a b"), Err(WriteError::NotToken)));
636 assert!(matches!(
637 w.fields("no-cache", &["a b"]),
638 Err(WriteError::NotToken)
639 ));
640 let mut small = [0u8; 3];
641 let mut out = crate::SliceSink::new(&mut small);
642 assert!(matches!(
643 Writer::new(&mut out).flag("public"),
644 Err(WriteError::Full)
645 ));
646 assert!(out.is_empty());
647 }
648}