use std::fmt; use std::str::FromStr; use header::{Header, HeaderFormat}; use header::parsing::{from_one_comma_delimited, fmt_comma_delimited}; /// The Cache-Control header. #[derive(PartialEq, Clone, Show)] pub struct CacheControl(pub Vec); deref!(CacheControl => Vec); impl Header for CacheControl { fn header_name(_: Option) -> &'static str { "Cache-Control" } fn parse_header(raw: &[Vec]) -> Option { let directives = raw.iter() .filter_map(|line| from_one_comma_delimited(&line[])) .collect::>>() .concat(); if directives.len() > 0 { Some(CacheControl(directives)) } else { None } } } impl HeaderFormat for CacheControl { fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt_comma_delimited(fmt, &self[]) } } /// CacheControl contains a list of these directives. #[derive(PartialEq, Clone, Show)] pub enum CacheDirective { /// "no-cache" NoCache, /// "no-store" NoStore, /// "no-transform" NoTransform, /// "only-if-cached" OnlyIfCached, // request directives /// "max-age=delta" MaxAge(u32), /// "max-stale=delta" MaxStale(u32), /// "min-fresh=delta" MinFresh(u32), // response directives /// "must-revalidate" MustRevalidate, /// "public" Public, /// "private" Private, /// "proxy-revalidate" ProxyRevalidate, /// "s-maxage=delta" SMaxAge(u32), /// Extension directives. Optionally include an argument. Extension(String, Option) } impl fmt::String for CacheDirective { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::CacheDirective::*; fmt::String::fmt(match *self { NoCache => "no-cache", NoStore => "no-store", NoTransform => "no-transform", OnlyIfCached => "only-if-cached", MaxAge(secs) => return write!(f, "max-age={}", secs), MaxStale(secs) => return write!(f, "max-stale={}", secs), MinFresh(secs) => return write!(f, "min-fresh={}", secs), MustRevalidate => "must-revalidate", Public => "public", Private => "private", ProxyRevalidate => "proxy-revalidate", SMaxAge(secs) => return write!(f, "s-maxage={}", secs), Extension(ref name, None) => &name[], Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), }, f) } } impl FromStr for CacheDirective { fn from_str(s: &str) -> Option { use self::CacheDirective::*; match s { "no-cache" => Some(NoCache), "no-store" => Some(NoStore), "no-transform" => Some(NoTransform), "only-if-cached" => Some(OnlyIfCached), "must-revalidate" => Some(MustRevalidate), "public" => Some(Public), "private" => Some(Private), "proxy-revalidate" => Some(ProxyRevalidate), "" => None, _ => match s.find('=') { Some(idx) if idx+1 < s.len() => match (&s[..idx], &s[idx+1..].trim_matches('"')) { ("max-age" , secs) => secs.parse().map(MaxAge), ("max-stale", secs) => secs.parse().map(MaxStale), ("min-fresh", secs) => secs.parse().map(MinFresh), ("s-maxage", secs) => secs.parse().map(SMaxAge), (left, right) => Some(Extension(left.to_string(), Some(right.to_string()))) }, Some(_) => None, None => Some(Extension(s.to_string(), None)) } } } } #[cfg(test)] mod tests { use header::Header; use super::*; #[test] fn test_parse_multiple_headers() { let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]); assert_eq!(cache, Some(CacheControl(vec![CacheDirective::NoCache, CacheDirective::Private]))) } #[test] fn test_parse_argument() { let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]); assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(100), CacheDirective::Private]))) } #[test] fn test_parse_quote_form() { let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]); assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) } #[test] fn test_parse_extension() { let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]); assert_eq!(cache, Some(CacheControl(vec![CacheDirective::Extension("foo".to_string(), None), CacheDirective::Extension("bar".to_string(), Some("baz".to_string()))]))) } #[test] fn test_parse_bad_syntax() { let cache: Option = Header::parse_header(&[b"foo=".to_vec()]); assert_eq!(cache, None) } } bench_header!(normal, CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] });