From 92ed9d7c2f281ea7064638b7f598897bfe91432f Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Mon, 20 Feb 2023 10:12:16 -0800 Subject: [PATCH 1/7] Add a FromStr implementation for ContentType (#129) --- src/common/content_type.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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!( From da9485ea41c9c5d68b8740d9f4add85b1f3238a3 Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Tue, 21 Feb 2023 13:43:11 -0800 Subject: [PATCH 2/7] s/seconds/duration in variable name to CacheControl (#131) I found the docs a bit confusing/inconsistent here (https://docs.rs/headers/0.3.8/headers/struct.CacheControl.html#method.with_max_age) since Duration needn't be specified in seconds. --- src/common/cache_control.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common/cache_control.rs b/src/common/cache_control.rs index 305361d3..06203cc4 100644 --- a/src/common/cache_control.rs +++ b/src/common/cache_control.rs @@ -159,26 +159,26 @@ impl CacheControl { } /// 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 } } From 583ec9e25355c15c0b5b61cde9a330bb2c8861e8 Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Tue, 21 Feb 2023 13:55:32 -0800 Subject: [PATCH 3/7] Add cargo fmt --check to CI (#130) * Add cargo fmt --check to CI Grabbed the incantation from https://github.com/hyperium/hyper/blob/master/.github/workflows/CI.yml Was noticing that my editor was automatically doing this - and figured it's good form to use an autoformatter with default settings. * Add components: rustfmt --- .github/workflows/ci.yml | 7 +++++++ src/common/content_range.rs | 33 +++++++++++++++++---------------- src/common/etag.rs | 4 +--- src/common/host.rs | 2 +- src/common/if_range.rs | 4 +++- src/common/origin.rs | 2 +- src/util/entity.rs | 13 +++++-------- src/util/flat_csv.rs | 8 ++++---- 8 files changed, 39 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3b965d6..22010924 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: toolchain: ${{ matrix.rust }} profile: minimal override: true + components: rustfmt - name: cargo test --all uses: actions-rs/cargo@v1 @@ -41,6 +42,12 @@ jobs: cargo update -Z minimal-versions cargo check + - name: cargo fmt --check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + MSRV: runs-on: ubuntu-latest 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/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/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() } From 0f78ab34046979106987bee5987d7fc5b436bc4c Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Wed, 22 Feb 2023 08:39:57 -0800 Subject: [PATCH 4/7] Add immutable flag to CacheControl (#132) RFC8246 adds this https://www.rfc-editor.org/rfc/rfc8246. Reasonable documentation here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control --- src/common/cache_control.rs | 47 ++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/common/cache_control.rs b/src/common/cache_control.rs index 06203cc4..85e48fcb 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 @@ -45,14 +46,15 @@ pub struct CacheControl { 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; + const NO_CACHE = 0b000000001; + const NO_STORE = 0b000000010; + const NO_TRANSFORM = 0b000000100; + const ONLY_IF_CACHED = 0b000001000; + const MUST_REVALIDATE = 0b000010000; + const PUBLIC = 0b000100000; + const PRIVATE = 0b001000000; + const PROXY_REVALIDATE = 0b010000000; + const IMMUTABLE = 0b100000000; } } @@ -100,6 +102,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,6 +165,12 @@ 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, duration: Duration) -> Self { self.max_age = Some(duration.into()); @@ -236,6 +249,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 +294,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 +342,7 @@ enum Directive { MustRevalidate, Public, Private, + Immutable, ProxyRevalidate, SMaxAge(u64), } @@ -345,6 +363,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 +383,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 +448,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] From de3743031f4df7a2936a75ed30531cd039294f83 Mon Sep 17 00:00:00 2001 From: tottoto Date: Wed, 22 Mar 2023 02:55:43 +0900 Subject: [PATCH 5/7] Remove bitflags (#135) --- Cargo.toml | 1 - src/common/cache_control.rs | 37 ++++++++++++++++++++++++++----------- src/lib.rs | 2 -- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b49c86e7..4fddd89a 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.13" -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 85e48fcb..afb69249 100644 --- a/src/common/cache_control.rs +++ b/src/common/cache_control.rs @@ -44,17 +44,32 @@ pub struct CacheControl { s_max_age: Option, } -bitflags! { - struct Flags: u32 { - const NO_CACHE = 0b000000001; - const NO_STORE = 0b000000010; - const NO_TRANSFORM = 0b000000100; - const ONLY_IF_CACHED = 0b000001000; - const MUST_REVALIDATE = 0b000010000; - const PUBLIC = 0b000100000; - const PRIVATE = 0b001000000; - const PROXY_REVALIDATE = 0b010000000; - const IMMUTABLE = 0b100000000; +#[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; } } 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; From f01cc90cf8d601a716856bc9d29f47df92b779e4 Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 21 Apr 2023 22:37:10 +0900 Subject: [PATCH 6/7] Update ci (#137) --- .github/workflows/ci.yml | 41 +++++++++------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22010924..fbd9179b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,52 +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 components: rustfmt - - - 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 - + - 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 - - - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --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 From 58eed8669dcd9d39098b4a481c3f0644206dc955 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sun, 22 Jan 2023 00:17:04 +0530 Subject: [PATCH 7/7] Update base64 to 0.21 Signed-off-by: Ayush Singh --- Cargo.toml | 2 +- src/common/authorization.rs | 8 +++++--- src/common/sec_websocket_accept.rs | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4fddd89a..ea13a911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ [dependencies] http = "0.2.0" headers-core = { version = "0.2", path = "./headers-core" } -base64 = "0.13" +base64 = "0.21" bytes = "1" mime = "0.3.14" sha1 = "0.10" diff --git a/src/common/authorization.rs b/src/common/authorization.rs index 734bc6e2..e62f2fd9 100644 --- a/src/common/authorization.rs +++ b/src/common/authorization.rs @@ -1,6 +1,7 @@ //! Authorization header and types. -use base64; +use base64::engine::general_purpose::STANDARD as ENGINE; +use base64::Engine; use bytes::Bytes; use util::HeaderValueString; @@ -158,7 +159,8 @@ impl Credentials for Basic { let bytes = &value.as_bytes()["Basic ".len()..]; let non_space_pos = bytes.iter().position(|b| *b != b' ')?; let bytes = &bytes[non_space_pos..]; - let bytes = base64::decode(bytes).ok()?; + + let bytes = ENGINE.decode(bytes).ok()?; let decoded = String::from_utf8(bytes).ok()?; @@ -169,7 +171,7 @@ impl Credentials for Basic { fn encode(&self) -> HeaderValue { let mut encoded = String::from("Basic "); - base64::encode_config_buf(&self.decoded, base64::STANDARD, &mut encoded); + ENGINE.encode_string(&self.decoded, &mut encoded); let bytes = Bytes::from(encoded); HeaderValue::from_maybe_shared(bytes) diff --git a/src/common/sec_websocket_accept.rs b/src/common/sec_websocket_accept.rs index 9e9176f0..89ec7c07 100644 --- a/src/common/sec_websocket_accept.rs +++ b/src/common/sec_websocket_accept.rs @@ -1,4 +1,5 @@ -use base64; +use base64::engine::general_purpose::STANDARD as ENGINE; +use base64::Engine; use bytes::Bytes; use sha1::{Digest, Sha1}; @@ -39,7 +40,7 @@ fn sign(key: &[u8]) -> SecWebsocketAccept { let mut sha1 = Sha1::default(); sha1.update(key); sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); - let b64 = Bytes::from(base64::encode(&sha1.finalize())); + let b64 = Bytes::from(ENGINE.encode(&sha1.finalize())); let val = ::HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value");