summaryrefslogtreecommitdiffstats
path: root/third_party/rust/headers/src/common/cache_control.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/headers/src/common/cache_control.rs')
-rw-r--r--third_party/rust/headers/src/common/cache_control.rs464
1 files changed, 464 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/common/cache_control.rs b/third_party/rust/headers/src/common/cache_control.rs
new file mode 100644
index 0000000000..305361d35b
--- /dev/null
+++ b/third_party/rust/headers/src/common/cache_control.rs
@@ -0,0 +1,464 @@
+use std::fmt;
+use std::iter::FromIterator;
+use std::str::FromStr;
+use std::time::Duration;
+
+use util::{self, csv, Seconds};
+use HeaderValue;
+
+/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
+///
+/// The `Cache-Control` header field is used to specify directives for
+/// caches along the request/response chain. Such cache directives are
+/// unidirectional in that the presence of a directive in a request does
+/// not imply that the same directive is to be given in the response.
+///
+/// ## ABNF
+///
+/// ```text
+/// Cache-Control = 1#cache-directive
+/// cache-directive = token [ "=" ( token / quoted-string ) ]
+/// ```
+///
+/// ## Example values
+///
+/// * `no-cache`
+/// * `private, community="UCI"`
+/// * `max-age=30`
+///
+/// # Example
+///
+/// ```
+/// # extern crate headers;
+/// use headers::CacheControl;
+///
+/// let cc = CacheControl::new();
+/// ```
+#[derive(PartialEq, Clone, Debug)]
+pub struct CacheControl {
+ flags: Flags,
+ max_age: Option<Seconds>,
+ max_stale: Option<Seconds>,
+ min_fresh: Option<Seconds>,
+ s_max_age: Option<Seconds>,
+}
+
+bitflags! {
+ struct Flags: u32 {
+ const NO_CACHE = 0b00000001;
+ const NO_STORE = 0b00000010;
+ const NO_TRANSFORM = 0b00000100;
+ const ONLY_IF_CACHED = 0b00001000;
+ const MUST_REVALIDATE = 0b00010000;
+ const PUBLIC = 0b00100000;
+ const PRIVATE = 0b01000000;
+ const PROXY_REVALIDATE = 0b10000000;
+ }
+}
+
+impl CacheControl {
+ /// Construct a new empty `CacheControl` header.
+ pub fn new() -> Self {
+ CacheControl {
+ flags: Flags::empty(),
+ max_age: None,
+ max_stale: None,
+ min_fresh: None,
+ s_max_age: None,
+ }
+ }
+
+ // getters
+
+ /// Check if the `no-cache` directive is set.
+ pub fn no_cache(&self) -> bool {
+ self.flags.contains(Flags::NO_CACHE)
+ }
+
+ /// Check if the `no-store` directive is set.
+ pub fn no_store(&self) -> bool {
+ self.flags.contains(Flags::NO_STORE)
+ }
+
+ /// Check if the `no-transform` directive is set.
+ pub fn no_transform(&self) -> bool {
+ self.flags.contains(Flags::NO_TRANSFORM)
+ }
+
+ /// Check if the `only-if-cached` directive is set.
+ pub fn only_if_cached(&self) -> bool {
+ self.flags.contains(Flags::ONLY_IF_CACHED)
+ }
+
+ /// Check if the `public` directive is set.
+ pub fn public(&self) -> bool {
+ self.flags.contains(Flags::PUBLIC)
+ }
+
+ /// Check if the `private` directive is set.
+ pub fn private(&self) -> bool {
+ self.flags.contains(Flags::PRIVATE)
+ }
+
+ /// Get the value of the `max-age` directive if set.
+ pub fn max_age(&self) -> Option<Duration> {
+ self.max_age.map(Into::into)
+ }
+
+ /// Get the value of the `max-stale` directive if set.
+ pub fn max_stale(&self) -> Option<Duration> {
+ self.max_stale.map(Into::into)
+ }
+
+ /// Get the value of the `min-fresh` directive if set.
+ pub fn min_fresh(&self) -> Option<Duration> {
+ self.min_fresh.map(Into::into)
+ }
+
+ /// Get the value of the `s-maxage` directive if set.
+ pub fn s_max_age(&self) -> Option<Duration> {
+ self.s_max_age.map(Into::into)
+ }
+
+ // setters
+
+ /// Set the `no-cache` directive.
+ pub fn with_no_cache(mut self) -> Self {
+ self.flags.insert(Flags::NO_CACHE);
+ self
+ }
+
+ /// Set the `no-store` directive.
+ pub fn with_no_store(mut self) -> Self {
+ self.flags.insert(Flags::NO_STORE);
+ self
+ }
+
+ /// Set the `no-transform` directive.
+ pub fn with_no_transform(mut self) -> Self {
+ self.flags.insert(Flags::NO_TRANSFORM);
+ self
+ }
+
+ /// Set the `only-if-cached` directive.
+ pub fn with_only_if_cached(mut self) -> Self {
+ self.flags.insert(Flags::ONLY_IF_CACHED);
+ self
+ }
+
+ /// Set the `private` directive.
+ pub fn with_private(mut self) -> Self {
+ self.flags.insert(Flags::PRIVATE);
+ self
+ }
+
+ /// Set the `public` directive.
+ pub fn with_public(mut self) -> Self {
+ self.flags.insert(Flags::PUBLIC);
+ self
+ }
+
+ /// Set the `max-age` directive.
+ pub fn with_max_age(mut self, seconds: Duration) -> Self {
+ self.max_age = Some(seconds.into());
+ self
+ }
+
+ /// Set the `max-stale` directive.
+ pub fn with_max_stale(mut self, seconds: Duration) -> Self {
+ self.max_stale = Some(seconds.into());
+ self
+ }
+
+ /// Set the `min-fresh` directive.
+ pub fn with_min_fresh(mut self, seconds: Duration) -> Self {
+ self.min_fresh = Some(seconds.into());
+ self
+ }
+
+ /// Set the `s-maxage` directive.
+ pub fn with_s_max_age(mut self, seconds: Duration) -> Self {
+ self.s_max_age = Some(seconds.into());
+ self
+ }
+}
+
+impl ::Header for CacheControl {
+ fn name() -> &'static ::HeaderName {
+ &::http::header::CACHE_CONTROL
+ }
+
+ fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
+ csv::from_comma_delimited(values).map(|FromIter(cc)| cc)
+ }
+
+ fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
+ values.extend(::std::iter::once(util::fmt(Fmt(self))));
+ }
+}
+
+// Adapter to be used in Header::decode
+struct FromIter(CacheControl);
+
+impl FromIterator<KnownDirective> for FromIter {
+ fn from_iter<I>(iter: I) -> Self
+ where
+ I: IntoIterator<Item = KnownDirective>,
+ {
+ let mut cc = CacheControl::new();
+
+ // ignore all unknown directives
+ let iter = iter.into_iter().filter_map(|dir| match dir {
+ KnownDirective::Known(dir) => Some(dir),
+ KnownDirective::Unknown => None,
+ });
+
+ for directive in iter {
+ match directive {
+ Directive::NoCache => {
+ cc.flags.insert(Flags::NO_CACHE);
+ }
+ Directive::NoStore => {
+ cc.flags.insert(Flags::NO_STORE);
+ }
+ Directive::NoTransform => {
+ cc.flags.insert(Flags::NO_TRANSFORM);
+ }
+ Directive::OnlyIfCached => {
+ cc.flags.insert(Flags::ONLY_IF_CACHED);
+ }
+ Directive::MustRevalidate => {
+ cc.flags.insert(Flags::MUST_REVALIDATE);
+ }
+ Directive::Public => {
+ cc.flags.insert(Flags::PUBLIC);
+ }
+ Directive::Private => {
+ cc.flags.insert(Flags::PRIVATE);
+ }
+ Directive::ProxyRevalidate => {
+ cc.flags.insert(Flags::PROXY_REVALIDATE);
+ }
+ Directive::MaxAge(secs) => {
+ cc.max_age = Some(Duration::from_secs(secs.into()).into());
+ }
+ Directive::MaxStale(secs) => {
+ cc.max_stale = Some(Duration::from_secs(secs.into()).into());
+ }
+ Directive::MinFresh(secs) => {
+ cc.min_fresh = Some(Duration::from_secs(secs.into()).into());
+ }
+ Directive::SMaxAge(secs) => {
+ cc.s_max_age = Some(Duration::from_secs(secs.into()).into());
+ }
+ }
+ }
+
+ FromIter(cc)
+ }
+}
+
+struct Fmt<'a>(&'a CacheControl);
+
+impl<'a> fmt::Display for Fmt<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let if_flag = |f: Flags, dir: Directive| {
+ if self.0.flags.contains(f) {
+ Some(dir)
+ } else {
+ None
+ }
+ };
+
+ let slice = &[
+ if_flag(Flags::NO_CACHE, Directive::NoCache),
+ if_flag(Flags::NO_STORE, Directive::NoStore),
+ if_flag(Flags::NO_TRANSFORM, Directive::NoTransform),
+ if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached),
+ if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate),
+ if_flag(Flags::PUBLIC, Directive::Public),
+ if_flag(Flags::PRIVATE, Directive::Private),
+ if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate),
+ self.0
+ .max_age
+ .as_ref()
+ .map(|s| Directive::MaxAge(s.as_u64())),
+ self.0
+ .max_stale
+ .as_ref()
+ .map(|s| Directive::MaxStale(s.as_u64())),
+ self.0
+ .min_fresh
+ .as_ref()
+ .map(|s| Directive::MinFresh(s.as_u64())),
+ self.0
+ .s_max_age
+ .as_ref()
+ .map(|s| Directive::SMaxAge(s.as_u64())),
+ ];
+
+ let iter = slice.iter().filter_map(|o| *o);
+
+ csv::fmt_comma_delimited(f, iter)
+ }
+}
+
+#[derive(Clone, Copy)]
+enum KnownDirective {
+ Known(Directive),
+ Unknown,
+}
+
+#[derive(Clone, Copy)]
+enum Directive {
+ NoCache,
+ NoStore,
+ NoTransform,
+ OnlyIfCached,
+
+ // request directives
+ MaxAge(u64),
+ MaxStale(u64),
+ MinFresh(u64),
+
+ // response directives
+ MustRevalidate,
+ Public,
+ Private,
+ ProxyRevalidate,
+ SMaxAge(u64),
+}
+
+impl fmt::Display for Directive {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(
+ match *self {
+ Directive::NoCache => "no-cache",
+ Directive::NoStore => "no-store",
+ Directive::NoTransform => "no-transform",
+ Directive::OnlyIfCached => "only-if-cached",
+
+ Directive::MaxAge(secs) => return write!(f, "max-age={}", secs),
+ Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs),
+ Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs),
+
+ Directive::MustRevalidate => "must-revalidate",
+ Directive::Public => "public",
+ Directive::Private => "private",
+ Directive::ProxyRevalidate => "proxy-revalidate",
+ Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
+ },
+ f,
+ )
+ }
+}
+
+impl FromStr for KnownDirective {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(KnownDirective::Known(match s {
+ "no-cache" => Directive::NoCache,
+ "no-store" => Directive::NoStore,
+ "no-transform" => Directive::NoTransform,
+ "only-if-cached" => Directive::OnlyIfCached,
+ "must-revalidate" => Directive::MustRevalidate,
+ "public" => Directive::Public,
+ "private" => Directive::Private,
+ "proxy-revalidate" => Directive::ProxyRevalidate,
+ "" => return Err(()),
+ _ => match s.find('=') {
+ Some(idx) if idx + 1 < s.len() => {
+ match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
+ ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?,
+ ("max-stale", secs) => {
+ secs.parse().map(Directive::MaxStale).map_err(|_| ())?
+ }
+ ("min-fresh", secs) => {
+ secs.parse().map(Directive::MinFresh).map_err(|_| ())?
+ }
+ ("s-maxage", secs) => {
+ secs.parse().map(Directive::SMaxAge).map_err(|_| ())?
+ }
+ _unknown => return Ok(KnownDirective::Unknown),
+ }
+ }
+ Some(_) | None => return Ok(KnownDirective::Unknown),
+ },
+ }))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::{test_decode, test_encode};
+ use super::*;
+
+ #[test]
+ fn test_parse_multiple_headers() {
+ assert_eq!(
+ test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(),
+ CacheControl::new().with_no_cache().with_private(),
+ );
+ }
+
+ #[test]
+ fn test_parse_argument() {
+ assert_eq!(
+ test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(),
+ CacheControl::new()
+ .with_max_age(Duration::from_secs(100))
+ .with_private(),
+ );
+ }
+
+ #[test]
+ fn test_parse_quote_form() {
+ assert_eq!(
+ test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(),
+ CacheControl::new().with_max_age(Duration::from_secs(200)),
+ );
+ }
+
+ #[test]
+ fn test_parse_extension() {
+ assert_eq!(
+ test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(),
+ CacheControl::new().with_no_cache(),
+ "unknown extensions are ignored but shouldn't fail parsing",
+ );
+ }
+
+ #[test]
+ fn test_parse_bad_syntax() {
+ assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None,);
+ }
+
+ #[test]
+ fn encode_one_flag_directive() {
+ let cc = CacheControl::new().with_no_cache();
+
+ let headers = test_encode(cc);
+ assert_eq!(headers["cache-control"], "no-cache");
+ }
+
+ #[test]
+ fn encode_one_param_directive() {
+ let cc = CacheControl::new().with_max_age(Duration::from_secs(300));
+
+ let headers = test_encode(cc);
+ assert_eq!(headers["cache-control"], "max-age=300");
+ }
+
+ #[test]
+ fn encode_two_directive() {
+ let headers = test_encode(CacheControl::new().with_no_cache().with_private());
+ assert_eq!(headers["cache-control"], "no-cache, private");
+
+ let headers = test_encode(
+ CacheControl::new()
+ .with_no_cache()
+ .with_max_age(Duration::from_secs(100)),
+ );
+ assert_eq!(headers["cache-control"], "no-cache, max-age=100");
+ }
+}