|
| 1 | +use std::fmt; |
| 2 | +use std::str::FromStr; |
| 3 | +use header::{Header, HeaderFormat}; |
| 4 | +use super::util::{from_one_comma_delimited, fmt_comma_delimited}; |
| 5 | + |
| 6 | +/// The Cache-Control header. |
| 7 | +#[deriving(PartialEq, Clone, Show)] |
| 8 | +pub struct CacheControl(pub Vec<CacheDirective>); |
| 9 | + |
| 10 | +deref!(CacheControl -> Vec<CacheDirective>) |
| 11 | + |
| 12 | +impl Header for CacheControl { |
| 13 | + fn header_name(_: Option<CacheControl>) -> &'static str { |
| 14 | + "Cache-Control" |
| 15 | + } |
| 16 | + |
| 17 | + fn parse_header(raw: &[Vec<u8>]) -> Option<CacheControl> { |
| 18 | + let directives = raw.iter() |
| 19 | + .filter_map(|line| from_one_comma_delimited(line[])) |
| 20 | + .collect::<Vec<Vec<CacheDirective>>>() |
| 21 | + .concat_vec(); |
| 22 | + if directives.len() > 0 { |
| 23 | + Some(CacheControl(directives)) |
| 24 | + } else { |
| 25 | + None |
| 26 | + } |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl HeaderFormat for CacheControl { |
| 31 | + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
| 32 | + fmt_comma_delimited(fmt, self[]) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +/// CacheControl contains a list of these directives. |
| 37 | +#[deriving(PartialEq, Clone)] |
| 38 | +pub enum CacheDirective { |
| 39 | + /// "no-cache" |
| 40 | + NoCache, |
| 41 | + /// "no-store" |
| 42 | + NoStore, |
| 43 | + /// "no-transform" |
| 44 | + NoTransform, |
| 45 | + /// "only-if-cached" |
| 46 | + OnlyIfCached, |
| 47 | + |
| 48 | + // request directives |
| 49 | + /// "max-age=delta" |
| 50 | + MaxAge(uint), |
| 51 | + /// "max-stale=delta" |
| 52 | + MaxStale(uint), |
| 53 | + /// "min-fresh=delta" |
| 54 | + MinFresh(uint), |
| 55 | + |
| 56 | + // response directives |
| 57 | + /// "must-revalidate" |
| 58 | + MustRevalidate, |
| 59 | + /// "public" |
| 60 | + Public, |
| 61 | + /// "private" |
| 62 | + Private, |
| 63 | + /// "proxy-revalidate" |
| 64 | + ProxyRevalidate, |
| 65 | + /// "s-maxage=delta" |
| 66 | + SMaxAge(uint), |
| 67 | + |
| 68 | + /// Extension directives. Optionally include an argument. |
| 69 | + Extension(String, Option<String>) |
| 70 | +} |
| 71 | + |
| 72 | +impl fmt::Show for CacheDirective { |
| 73 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 74 | + use self::CacheDirective::*; |
| 75 | + match *self { |
| 76 | + NoCache => "no-cache", |
| 77 | + NoStore => "no-store", |
| 78 | + NoTransform => "no-transform", |
| 79 | + OnlyIfCached => "only-if-cached", |
| 80 | + |
| 81 | + MaxAge(secs) => return write!(f, "max-age={}", secs), |
| 82 | + MaxStale(secs) => return write!(f, "max-stale={}", secs), |
| 83 | + MinFresh(secs) => return write!(f, "min-fresh={}", secs), |
| 84 | + |
| 85 | + MustRevalidate => "must-revalidate", |
| 86 | + Public => "public", |
| 87 | + Private => "private", |
| 88 | + ProxyRevalidate => "proxy-revalidate", |
| 89 | + SMaxAge(secs) => return write!(f, "s-maxage={}", secs), |
| 90 | + |
| 91 | + Extension(ref name, None) => name[], |
| 92 | + Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), |
| 93 | + |
| 94 | + }.fmt(f) |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +impl FromStr for CacheDirective { |
| 99 | + fn from_str(s: &str) -> Option<CacheDirective> { |
| 100 | + use self::CacheDirective::*; |
| 101 | + match s { |
| 102 | + "no-cache" => Some(NoCache), |
| 103 | + "no-store" => Some(NoStore), |
| 104 | + "no-transform" => Some(NoTransform), |
| 105 | + "only-if-cached" => Some(OnlyIfCached), |
| 106 | + "must-revalidate" => Some(MustRevalidate), |
| 107 | + "public" => Some(Public), |
| 108 | + "private" => Some(Private), |
| 109 | + "proxy-revalidate" => Some(ProxyRevalidate), |
| 110 | + "" => None, |
| 111 | + _ => match s.find('=') { |
| 112 | + Some(idx) if idx+1 < s.len() => match (s[..idx], s[idx+1..].trim_chars('"')) { |
| 113 | + ("max-age" , secs) => from_str::<uint>(secs).map(MaxAge), |
| 114 | + ("max-stale", secs) => from_str::<uint>(secs).map(MaxStale), |
| 115 | + ("min-fresh", secs) => from_str::<uint>(secs).map(MinFresh), |
| 116 | + ("s-maxage", secs) => from_str::<uint>(secs).map(SMaxAge), |
| 117 | + (left, right) => Some(Extension(left.into_string(), Some(right.into_string()))) |
| 118 | + }, |
| 119 | + Some(_) => None, |
| 120 | + None => Some(Extension(s.into_string(), None)) |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +#[cfg(test)] |
| 127 | +mod tests { |
| 128 | + use header::Header; |
| 129 | + use super::*; |
| 130 | + |
| 131 | + #[test] |
| 132 | + fn test_parse_multiple_headers() { |
| 133 | + let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]); |
| 134 | + assert_eq!(cache, Some(CacheControl(vec![CacheDirective::NoCache, |
| 135 | + CacheDirective::Private]))) |
| 136 | + } |
| 137 | + |
| 138 | + #[test] |
| 139 | + fn test_parse_argument() { |
| 140 | + let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]); |
| 141 | + assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(100), |
| 142 | + CacheDirective::Private]))) |
| 143 | + } |
| 144 | + |
| 145 | + #[test] |
| 146 | + fn test_parse_quote_form() { |
| 147 | + let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]); |
| 148 | + assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) |
| 149 | + } |
| 150 | + |
| 151 | + #[test] |
| 152 | + fn test_parse_extension() { |
| 153 | + let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]); |
| 154 | + assert_eq!(cache, Some(CacheControl(vec![CacheDirective::Extension("foo".to_string(), None), |
| 155 | + CacheDirective::Extension("bar".to_string(), Some("baz".to_string()))]))) |
| 156 | + } |
| 157 | + |
| 158 | + #[test] |
| 159 | + fn test_parse_bad_syntax() { |
| 160 | + let cache: Option<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]); |
| 161 | + assert_eq!(cache, None) |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +bench_header!(normal, CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] }) |
0 commit comments