diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3b965d6..fbd9179b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,45 +15,29 @@ jobs: rust: [stable, beta, nightly] steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - profile: minimal - override: true - - - name: cargo test --all - uses: actions-rs/cargo@v1 - with: - command: test - args: --all - - name: cargo test --benches - if: matrix.rust == 'nightly' - uses: actions-rs/cargo@v1 - with: - command: test - args: --benches - + components: rustfmt + - run: cargo test --workspace + - if: matrix.rust == 'nightly' + run: cargo test --benches - name: Check minimal versions if: matrix.rust == 'nightly' run: | cargo clean cargo update -Z minimal-versions cargo check + - run: cargo fmt --all --check MSRV: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install rust ${{ env.minrust }} - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.minrust }} - profile: minimal - override: true - - - name: cargo build - uses: actions-rs/cargo@v1 - with: - command: build + - run: cargo build diff --git a/Cargo.toml b/Cargo.toml index 5e682313..ea13a911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ members = [ http = "0.2.0" headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.21" -bitflags = "1.0" bytes = "1" mime = "0.3.14" sha1 = "0.10" diff --git a/src/common/cache_control.rs b/src/common/cache_control.rs index 305361d3..afb69249 100644 --- a/src/common/cache_control.rs +++ b/src/common/cache_control.rs @@ -7,6 +7,7 @@ use util::{self, csv, Seconds}; use HeaderValue; /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) +/// with extensions in [RFC8246](https://www.rfc-editor.org/rfc/rfc8246) /// /// The `Cache-Control` header field is used to specify directives for /// caches along the request/response chain. Such cache directives are @@ -43,16 +44,32 @@ pub struct CacheControl { s_max_age: Option, } -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; +#[derive(Debug, Clone, PartialEq)] +struct Flags { + bits: u64, +} + +impl Flags { + const NO_CACHE: Self = Self { bits: 0b000000001 }; + const NO_STORE: Self = Self { bits: 0b000000010 }; + const NO_TRANSFORM: Self = Self { bits: 0b000000100 }; + const ONLY_IF_CACHED: Self = Self { bits: 0b000001000 }; + const MUST_REVALIDATE: Self = Self { bits: 0b000010000 }; + const PUBLIC: Self = Self { bits: 0b000100000 }; + const PRIVATE: Self = Self { bits: 0b001000000 }; + const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 }; + const IMMUTABLE: Self = Self { bits: 0b100000000 }; + + fn empty() -> Self { + Self { bits: 0 } + } + + fn contains(&self, flag: Self) -> bool { + (self.bits & flag.bits) != 0 + } + + fn insert(&mut self, flag: Self) { + self.bits |= flag.bits; } } @@ -100,6 +117,11 @@ impl CacheControl { self.flags.contains(Flags::PRIVATE) } + /// Check if the `immutable` directive is set. + pub fn immutable(&self) -> bool { + self.flags.contains(Flags::IMMUTABLE) + } + /// Get the value of the `max-age` directive if set. pub fn max_age(&self) -> Option { self.max_age.map(Into::into) @@ -158,27 +180,33 @@ impl CacheControl { self } + /// Set the `immutable` directive. + pub fn with_immutable(mut self) -> Self { + self.flags.insert(Flags::IMMUTABLE); + self + } + /// Set the `max-age` directive. - pub fn with_max_age(mut self, seconds: Duration) -> Self { - self.max_age = Some(seconds.into()); + pub fn with_max_age(mut self, duration: Duration) -> Self { + self.max_age = Some(duration.into()); self } /// Set the `max-stale` directive. - pub fn with_max_stale(mut self, seconds: Duration) -> Self { - self.max_stale = Some(seconds.into()); + pub fn with_max_stale(mut self, duration: Duration) -> Self { + self.max_stale = Some(duration.into()); self } /// Set the `min-fresh` directive. - pub fn with_min_fresh(mut self, seconds: Duration) -> Self { - self.min_fresh = Some(seconds.into()); + pub fn with_min_fresh(mut self, duration: Duration) -> Self { + self.min_fresh = Some(duration.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()); + pub fn with_s_max_age(mut self, duration: Duration) -> Self { + self.s_max_age = Some(duration.into()); self } } @@ -236,6 +264,9 @@ impl FromIterator for FromIter { Directive::Private => { cc.flags.insert(Flags::PRIVATE); } + Directive::Immutable => { + cc.flags.insert(Flags::IMMUTABLE); + } Directive::ProxyRevalidate => { cc.flags.insert(Flags::PROXY_REVALIDATE); } @@ -278,6 +309,7 @@ impl<'a> fmt::Display for Fmt<'a> { if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate), if_flag(Flags::PUBLIC, Directive::Public), if_flag(Flags::PRIVATE, Directive::Private), + if_flag(Flags::IMMUTABLE, Directive::Immutable), if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate), self.0 .max_age @@ -325,6 +357,7 @@ enum Directive { MustRevalidate, Public, Private, + Immutable, ProxyRevalidate, SMaxAge(u64), } @@ -345,6 +378,7 @@ impl fmt::Display for Directive { Directive::MustRevalidate => "must-revalidate", Directive::Public => "public", Directive::Private => "private", + Directive::Immutable => "immutable", Directive::ProxyRevalidate => "proxy-revalidate", Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs), }, @@ -364,6 +398,7 @@ impl FromStr for KnownDirective { "must-revalidate" => Directive::MustRevalidate, "public" => Directive::Public, "private" => Directive::Private, + "immutable" => Directive::Immutable, "proxy-revalidate" => Directive::ProxyRevalidate, "" => return Err(()), _ => match s.find('=') { @@ -428,9 +463,18 @@ mod tests { ); } + #[test] + fn test_immutable() { + let cc = CacheControl::new().with_immutable(); + let headers = test_encode(cc.clone()); + assert_eq!(headers["cache-control"], "immutable"); + assert_eq!(test_decode::(&["immutable"]).unwrap(), cc); + assert!(cc.immutable()); + } + #[test] fn test_parse_bad_syntax() { - assert_eq!(test_decode::(&["max-age=lolz"]), None,); + assert_eq!(test_decode::(&["max-age=lolz"]), None); } #[test] diff --git a/src/common/content_range.rs b/src/common/content_range.rs index 7ed2b200..65cd7965 100644 --- a/src/common/content_range.rs +++ b/src/common/content_range.rs @@ -178,22 +178,23 @@ fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { } /* - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - complete_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - complete_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], +test_header!(test_bytes, + vec![b"bytes 0-499/500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + complete_length: Some(500) + }))); + +test_header!(test_bytes_unknown_len, + vec![b"bytes 0-499/*"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + complete_length: None + }))); + +test_header!(test_bytes_unknown_range, + vec![b"bytes */ +500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, complete_length: Some(500) diff --git a/src/common/content_type.rs b/src/common/content_type.rs index bfe56527..1ae15c2e 100644 --- a/src/common/content_type.rs +++ b/src/common/content_type.rs @@ -135,6 +135,16 @@ impl fmt::Display for ContentType { } } +impl std::str::FromStr for ContentType { + type Err = ::Error; + + fn from_str(s: &str) -> Result { + s.parse::() + .map(|m| m.into()) + .map_err(|_| ::Error::invalid()) + } +} + #[cfg(test)] mod tests { use super::super::test_decode; @@ -148,6 +158,15 @@ mod tests { ); } + #[test] + fn from_str() { + assert_eq!( + "application/json".parse::().unwrap(), + ContentType::json(), + ); + assert!("invalid-mimetype".parse::().is_err()); + } + bench_header!(bench_plain, ContentType, "text/plain"); bench_header!(bench_json, ContentType, "application/json"); bench_header!( diff --git a/src/common/etag.rs b/src/common/etag.rs index 1bbd0740..25846b76 100644 --- a/src/common/etag.rs +++ b/src/common/etag.rs @@ -50,9 +50,7 @@ error_type!(InvalidETag); impl FromStr for ETag { type Err = InvalidETag; fn from_str(src: &str) -> Result { - let val = src - .parse() - .map_err(|_| InvalidETag { _inner: () })?; + let val = src.parse().map_err(|_| InvalidETag { _inner: () })?; EntityTag::from_owned(val) .map(ETag) diff --git a/src/common/host.rs b/src/common/host.rs index a5c41b1d..7c0d7acd 100644 --- a/src/common/host.rs +++ b/src/common/host.rs @@ -1,5 +1,5 @@ -use std::fmt; use std::convert::TryFrom; +use std::fmt; use http::uri::Authority; diff --git a/src/common/if_range.rs b/src/common/if_range.rs index 38480bbe..e2675b43 100644 --- a/src/common/if_range.rs +++ b/src/common/if_range.rs @@ -64,7 +64,9 @@ impl IfRange { pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool { match self.0 { IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true), - IfRange_::EntityTag(ref entity) => etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true), + IfRange_::EntityTag(ref entity) => { + etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true) + } } } } diff --git a/src/common/origin.rs b/src/common/origin.rs index 09349f5e..6d4a022e 100644 --- a/src/common/origin.rs +++ b/src/common/origin.rs @@ -1,5 +1,5 @@ -use std::fmt; use std::convert::TryFrom; +use std::fmt; use bytes::Bytes; use http::uri::{self, Authority, Scheme, Uri}; diff --git a/src/lib.rs b/src/lib.rs index 0d27a810..971d5677 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,8 +73,6 @@ //! ``` extern crate base64; -#[macro_use] -extern crate bitflags; extern crate bytes; extern crate headers_core; extern crate http; diff --git a/src/util/entity.rs b/src/util/entity.rs index c966b6fe..67604be4 100644 --- a/src/util/entity.rs +++ b/src/util/entity.rs @@ -167,9 +167,7 @@ impl EntityTag { } pub(crate) fn from_val(val: &HeaderValue) -> Option { - EntityTag::parse(val.as_bytes()).map(|_entity| { - EntityTag(val.clone()) - }) + EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone())) } } @@ -239,11 +237,10 @@ impl EntityTagRange { { match *self { EntityTagRange::Any => true, - EntityTagRange::Tags(ref tags) => { - tags.iter() - .flat_map(EntityTag::<&str>::parse) - .any(|tag| func(&tag, entity)) - }, + EntityTagRange::Tags(ref tags) => tags + .iter() + .flat_map(EntityTag::<&str>::parse) + .any(|tag| func(&tag, entity)), } } } diff --git a/src/util/flat_csv.rs b/src/util/flat_csv.rs index 099b0342..7be56c87 100644 --- a/src/util/flat_csv.rs +++ b/src/util/flat_csv.rs @@ -120,8 +120,8 @@ impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv { buf.extend_from_slice(val.as_bytes()); } - let val = - HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid"); + let val = HeaderValue::from_maybe_shared(buf.freeze()) + .expect("comma separated HeaderValues are valid"); val.into() } @@ -151,8 +151,8 @@ impl FromIterator for FlatCsv { buf.extend_from_slice(val.as_bytes()); } - let val = - HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid"); + let val = HeaderValue::from_maybe_shared(buf.freeze()) + .expect("comma separated HeaderValues are valid"); val.into() }