From d8ab6af16a4897683515bd6d20a6156e95e59ad0 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 22 Mar 2020 14:15:49 -0400 Subject: [PATCH 01/57] inital commit, added fields to content_encoding, creating a QualityValue util type, creating a basic accept_encoding header that uses QualityValue --- Cargo.toml | 1 + src/common/accept_encoding.rs | 14 +++ src/common/content_encoding.rs | 24 +++++ src/common/mod.rs | 4 +- src/lib.rs | 1 + src/util/mod.rs | 4 +- src/util/quality_value.rs | 175 +++++++++++++++++++++++++++++++++ 7 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 src/common/accept_encoding.rs create mode 100644 src/util/quality_value.rs diff --git a/Cargo.toml b/Cargo.toml index a88f18e7..4a108ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.12" bitflags = "1.0" bytes = "0.5" +itertools = "0.9" mime = "0.3.14" sha-1 = "0.8" time = "0.1.34" diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs new file mode 100644 index 00000000..26023d4b --- /dev/null +++ b/src/common/accept_encoding.rs @@ -0,0 +1,14 @@ +use util::QualityValue; +// use HeaderValue; + + +/// `Accept-Encoding` header, defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.4) +/// +#[derive(Clone, Debug)] +pub struct AcceptEncoding(QualityValue); + +derive_header! { + AcceptEncoding(_), + name: ACCEPT_ENCODING +} diff --git a/src/common/content_encoding.rs b/src/common/content_encoding.rs index 444eb41c..e50be4bb 100644 --- a/src/common/content_encoding.rs +++ b/src/common/content_encoding.rs @@ -46,6 +46,30 @@ impl ContentEncoding { ContentEncoding(HeaderValue::from_static("gzip").into()) } + /// A constructor to easily create a `Content-Encoding: compress` header. + #[inline] + pub fn compress() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("compress").into()) + } + + /// A constructor to easily create a `Content-Encoding: deflate` header. + #[inline] + pub fn deflate() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("deflate").into()) + } + + /// A constructor to easily create a `Content-Encoding: identity` header. + #[inline] + pub fn identity() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("identity").into()) + } + + /// A constructor to easily create a `Content-Encoding: br` header. + #[inline] + pub fn br() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("br").into()) + } + /// Check if this header contains a given "coding". /// /// This can be used with these argument types: diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a1e9c0f..db6234b0 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -7,7 +7,7 @@ //! is used, such as `ContentType(pub Mime)`. //pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; +pub use self::accept_encoding::AcceptEncoding; //pub use self::accept_language::AcceptLanguage; pub use self::accept_ranges::AcceptRanges; //pub use self::accept::Accept; @@ -127,7 +127,7 @@ macro_rules! bench_header { //mod accept; //mod accept_charset; -//mod accept_encoding; +mod accept_encoding; //mod accept_language; mod accept_ranges; mod access_control_allow_credentials; diff --git a/src/lib.rs b/src/lib.rs index bf05e9fa..c1a48da5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ extern crate bitflags; extern crate bytes; extern crate headers_core; extern crate http; +extern crate itertools; extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))] diff --git a/src/util/mod.rs b/src/util/mod.rs index 07fddbfb..58b7715a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -8,7 +8,7 @@ pub(crate) use self::fmt::fmt; pub(crate) use self::http_date::HttpDate; pub(crate) use self::iter::IterExt; //pub use language_tags::LanguageTag; -//pub use self::quality_value::{Quality, QualityValue}; +pub(crate) use self::quality_value::QualityValue; pub(crate) use self::seconds::Seconds; pub(crate) use self::value_string::HeaderValueString; @@ -20,7 +20,7 @@ mod flat_csv; mod fmt; mod http_date; mod iter; -//mod quality_value; +mod quality_value; mod seconds; mod value_string; diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs new file mode 100644 index 00000000..adea9402 --- /dev/null +++ b/src/util/quality_value.rs @@ -0,0 +1,175 @@ +use std::cmp::Ordering; +use std::convert::TryFrom; +use std::marker::PhantomData; + +use HeaderValue; +use itertools::Itertools; +use util::{FlatCsv, TryFromValues}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct QualityValue { + csv: FlatCsv, + _marker: PhantomData, +} + +pub(crate) trait QualityDelimiter { + const STR: &'static str; +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) enum SemiQ {} + +impl QualityDelimiter for SemiQ { + const STR: &'static str = ";q="; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct QualityMeta<'a, Sep = SemiQ> { + pub data: &'a str, + pub quality: u16, + _marker: PhantomData, +} + +impl Ord for QualityMeta<'_, Delm> { + fn cmp(&self, other: &Self) -> Ordering { + other.quality.cmp(&self.quality) + } +} + +impl PartialOrd for QualityMeta<'_, Delm> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a, Delm: QualityDelimiter> TryFrom<&'a str> for QualityMeta<'a, Delm> { + type Error = ::Error; + + fn try_from(val: &'a str) -> Result { + let mut parts: Vec<&str> = val.split(Delm::STR).collect(); + + match (parts.pop(), parts.pop()) { + (Some(qual), Some(data)) => { + let parsed: f32 = qual.parse().map_err(|_| ::Error::invalid())?; + let quality = (parsed * 1000_f32) as u16; + + Ok(QualityMeta { + data, + quality, + _marker: PhantomData, + }) + } + // No deliter present, assign a quality value of 1 + (Some(data), None) => Ok(QualityMeta { + data, + quality: 1000_u16, + _marker: PhantomData, + }), + _ => Err(::Error::invalid()), + } + } +} + +impl QualityValue { + pub(crate) fn iter(&self) -> impl Iterator { + self.csv + .iter() + .map(|v| QualityMeta::::try_from(v).unwrap()) + .into_iter() + .sorted() + .map(|pair| pair.data) + .into_iter() + } +} + +impl From for QualityValue { + fn from(csv: FlatCsv) -> Self { + QualityValue { + csv, + _marker: PhantomData, + } + } +} + +impl From for QualityValue { + fn from(value: HeaderValue) -> Self { + QualityValue { + csv: value.into(), + _marker: PhantomData, + } + } +} + +impl<'a, Delm> From<&'a QualityValue> for HeaderValue { + fn from(qual: &'a QualityValue) -> HeaderValue { + qual.csv.value.clone() + } +} + +impl TryFromValues for QualityValue { + fn try_from_values<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + let flat: FlatCsv = values.collect(); + Ok(QualityValue::from(flat)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use util::flat_csv::Comma; + use HeaderValue; + + #[test] + fn multiple_qualities() { + let val = HeaderValue::from_static("gzip;q=1, br;q=0.8"); + let csv = FlatCsv::::from(val); + let qual = QualityValue::::from(csv); + + let mut values = qual.iter(); + assert_eq!(values.next(), Some("gzip")); + assert_eq!(values.next(), Some("br")); + assert_eq!(values.next(), None); + } + + #[test] + fn multiple_qualities_wrong_order() { + let val = HeaderValue::from_static("br;q=0.8, gzip;q=1"); + let csv = FlatCsv::::from(val); + let qual = QualityValue::::from(csv); + + let mut values = qual.iter(); + assert_eq!(values.next(), Some("gzip")); + assert_eq!(values.next(), Some("br")); + assert_eq!(values.next(), None); + } + + #[test] + fn multiple_values() { + let val = HeaderValue::from_static("deflate, gzip;q=1, br;q=0.8"); + let csv = FlatCsv::::from(val); + let qual = QualityValue::::from(csv); + + let mut values = qual.iter(); + assert_eq!(values.next(), Some("deflate")); + assert_eq!(values.next(), Some("gzip")); + assert_eq!(values.next(), Some("br")); + assert_eq!(values.next(), None); + } + + #[test] + fn multiple_values_wrong_order() { + let val = HeaderValue::from_static("deflate, br;q=0.8, gzip;q=1, *;q=0.1"); + let csv = FlatCsv::::from(val); + let qual = QualityValue::::from(csv); + + let mut values = qual.iter(); + assert_eq!(values.next(), Some("deflate")); + assert_eq!(values.next(), Some("gzip")); + assert_eq!(values.next(), Some("br")); + assert_eq!(values.next(), Some("*")); + assert_eq!(values.next(), None); + } +} From 2d14c194beec66d6b83e26cd9b36638b40b57fcd Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 22 Mar 2020 14:22:26 -0400 Subject: [PATCH 02/57] adding an alternate delimiter --- src/util/quality_value.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index adea9402..7ba5de7f 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -16,6 +16,7 @@ pub(crate) trait QualityDelimiter { const STR: &'static str; } +/// enum that represents the ';q=' delimiter #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub(crate) enum SemiQ {} @@ -23,6 +24,14 @@ impl QualityDelimiter for SemiQ { const STR: &'static str = ";q="; } +/// enum that represents the ';level=' delimiter (extremely rare) +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) enum SemiLevel {} + +impl QualityDelimiter for SemiLevel { + const STR: &'static str = ";level="; +} + #[derive(Clone, Debug, PartialEq, Eq)] struct QualityMeta<'a, Sep = SemiQ> { pub data: &'a str, @@ -119,14 +128,12 @@ impl TryFromValues for QualityValue { #[cfg(test)] mod tests { use super::*; - use util::flat_csv::Comma; use HeaderValue; #[test] fn multiple_qualities() { let val = HeaderValue::from_static("gzip;q=1, br;q=0.8"); - let csv = FlatCsv::::from(val); - let qual = QualityValue::::from(csv); + let qual = QualityValue::::from(val); let mut values = qual.iter(); assert_eq!(values.next(), Some("gzip")); @@ -137,8 +144,7 @@ mod tests { #[test] fn multiple_qualities_wrong_order() { let val = HeaderValue::from_static("br;q=0.8, gzip;q=1"); - let csv = FlatCsv::::from(val); - let qual = QualityValue::::from(csv); + let qual = QualityValue::::from(val); let mut values = qual.iter(); assert_eq!(values.next(), Some("gzip")); @@ -149,8 +155,7 @@ mod tests { #[test] fn multiple_values() { let val = HeaderValue::from_static("deflate, gzip;q=1, br;q=0.8"); - let csv = FlatCsv::::from(val); - let qual = QualityValue::::from(csv); + let qual = QualityValue::::from(val); let mut values = qual.iter(); assert_eq!(values.next(), Some("deflate")); @@ -162,8 +167,7 @@ mod tests { #[test] fn multiple_values_wrong_order() { let val = HeaderValue::from_static("deflate, br;q=0.8, gzip;q=1, *;q=0.1"); - let csv = FlatCsv::::from(val); - let qual = QualityValue::::from(csv); + let qual = QualityValue::::from(val); let mut values = qual.iter(); assert_eq!(values.next(), Some("deflate")); @@ -172,4 +176,16 @@ mod tests { assert_eq!(values.next(), Some("*")); assert_eq!(values.next(), None); } + + #[test] + fn alternate_delimiter() { + let val = HeaderValue::from_static("deflate, br;level=0.8, gzip;level=1"); + let qual = QualityValue::::from(val); + + let mut values = qual.iter(); + assert_eq!(values.next(), Some("deflate")); + assert_eq!(values.next(), Some("gzip")); + assert_eq!(values.next(), Some("br")); + assert_eq!(values.next(), None); + } } From 0879ada3c293855bc8130f79517848dcb169b9f7 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 22 Mar 2020 17:06:16 -0400 Subject: [PATCH 03/57] flushing out impl of Accept-Encoding header, adding convience methods --- src/common/accept_encoding.rs | 128 ++++++++++++++++++++++++++++++++-- src/util/quality_value.rs | 27 +++++-- 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs index 26023d4b..f876da71 100644 --- a/src/common/accept_encoding.rs +++ b/src/common/accept_encoding.rs @@ -1,10 +1,27 @@ -use util::QualityValue; -// use HeaderValue; - +use std::convert::TryFrom; +use util::{QualityValue, TryFromValues}; +use HeaderValue; /// `Accept-Encoding` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.4) -/// +/// +/// The `Accept-Encoding` header field can be used by user agents to +/// indicate what response content-codings are acceptable in the response. +/// An "identity" token is used as a synonym for "no encoding" in +/// order to communicate when no encoding is preferred. +/// +/// # ABNF +/// +/// ```text +/// Accept-Encoding = #( codings [ weight ] ) +/// codings = content-coding / "identity" / "*" +/// ``` +/// +/// # Example Values +/// +/// * `gzip` +/// * `br;q=1.0, gzip;q=0.8` +/// #[derive(Clone, Debug)] pub struct AcceptEncoding(QualityValue); @@ -12,3 +29,106 @@ derive_header! { AcceptEncoding(_), name: ACCEPT_ENCODING } + +impl AcceptEncoding { + /// Convience method + #[inline] + pub fn gzip() -> AcceptEncoding { + AcceptEncoding(HeaderValue::from_static("gzip").into()) + } + + /// A convience method to create an Accept-Encoding header from pairs of values and qualities + /// + /// # Example + /// + /// ``` + /// use headers::AcceptEncoding; + /// + /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; + /// let header = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()); + /// ``` + pub fn from_quality_pairs<'i, I>(pairs: &mut I) -> Result + where + I: Iterator, + { + let values: Vec = pairs + .map(|pair| { + QualityValue::try_from(pair).map(|qual: QualityValue| HeaderValue::from(qual)) + }) + .collect::, ::Error>>()?; + let value = QualityValue::try_from_values(&mut values.iter())?; + Ok(AcceptEncoding(value)) + } + + /// Returns the most prefered encoding that is specified by the client, + /// if one is specified. + /// + /// Note: This peeks at the underlying iter, not modifying it. + /// + /// # Example + /// + /// ``` + /// use headers::AcceptEncoding; + /// + /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; + /// let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); + /// let mut encodings = accept_enc.sorted_encodings(); + /// + /// assert_eq!(accept_enc.prefered_encoding(), Some("gzip")); + /// ``` + pub fn prefered_encoding(&self) -> Option<&str> { + self.0.iter().peekable().peek().map(|s| *s) + } + + /// Returns a quality sorted iterator of the accepted encodings + /// + /// # Example + /// + /// ``` + /// use headers::{AcceptEncoding, HeaderValue}; + /// + /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; + /// let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); + /// let mut encodings = accept_enc.sorted_encodings(); + /// + /// assert_eq!(encodings.next(), Some("gzip")); + /// assert_eq!(encodings.next(), Some("deflate")); + /// assert_eq!(encodings.next(), None); + /// ``` + pub fn sorted_encodings(&self) -> impl Iterator { + self.0.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use HeaderValue; + + #[test] + fn from_static() { + let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.9"); + let accept_enc = AcceptEncoding(val.into()); + + assert_eq!(accept_enc.prefered_encoding(), Some("deflate")); + + let mut encodings = accept_enc.sorted_encodings(); + assert_eq!(encodings.next(), Some("deflate")); + assert_eq!(encodings.next(), Some("gzip")); + assert_eq!(encodings.next(), Some("br")); + assert_eq!(encodings.next(), None); + } + + #[test] + fn from_pairs() { + let pairs = vec![("gzip", 1.0), ("br", 0.9)]; + let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); + + assert_eq!(accept_enc.prefered_encoding(), Some("gzip")); + + let mut encodings = accept_enc.sorted_encodings(); + assert_eq!(encodings.next(), Some("gzip")); + assert_eq!(encodings.next(), Some("br")); + assert_eq!(encodings.next(), None); + } +} diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index 7ba5de7f..4c091131 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -1,10 +1,10 @@ use std::cmp::Ordering; -use std::convert::TryFrom; +use std::convert::{From, TryFrom}; use std::marker::PhantomData; -use HeaderValue; use itertools::Itertools; use util::{FlatCsv, TryFromValues}; +use HeaderValue; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct QualityValue { @@ -100,6 +100,19 @@ impl From for QualityValue { } } +impl> TryFrom<(&str, F)> for QualityValue { + type Error = ::Error; + + fn try_from(pair: (&str, F)) -> Result { + let value = HeaderValue::try_from(format!("{}{}{}", pair.0, Delm::STR, pair.1.into())) + .map_err(|_e| ::Error::invalid())?; + Ok(QualityValue { + csv: value.into(), + _marker: PhantomData, + }) + } +} + impl From for QualityValue { fn from(value: HeaderValue) -> Self { QualityValue { @@ -115,9 +128,15 @@ impl<'a, Delm> From<&'a QualityValue> for HeaderValue { } } +impl From> for HeaderValue { + fn from(qual: QualityValue) -> HeaderValue { + qual.csv.value + } +} + impl TryFromValues for QualityValue { fn try_from_values<'i, I>(values: &mut I) -> Result - where + where I: Iterator, { let flat: FlatCsv = values.collect(); @@ -143,7 +162,7 @@ mod tests { #[test] fn multiple_qualities_wrong_order() { - let val = HeaderValue::from_static("br;q=0.8, gzip;q=1"); + let val = HeaderValue::from_static("br;q=0.8, gzip;q=1.0"); let qual = QualityValue::::from(val); let mut values = qual.iter(); From aa46020ce13e80e641a653fab834c2b82972ed1e Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 22 Mar 2020 17:39:42 -0400 Subject: [PATCH 04/57] updating comments --- src/common/accept_encoding.rs | 12 ++++++------ src/util/quality_value.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs index f876da71..5c5bf3b2 100644 --- a/src/common/accept_encoding.rs +++ b/src/common/accept_encoding.rs @@ -31,19 +31,19 @@ derive_header! { } impl AcceptEncoding { - /// Convience method + /// Convience method to create an `Accept-Encoding: gzip` header #[inline] pub fn gzip() -> AcceptEncoding { AcceptEncoding(HeaderValue::from_static("gzip").into()) } /// A convience method to create an Accept-Encoding header from pairs of values and qualities - /// - /// # Example - /// + /// + /// # Example + /// /// ``` /// use headers::AcceptEncoding; - /// + /// /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; /// let header = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()); /// ``` @@ -60,7 +60,7 @@ impl AcceptEncoding { Ok(AcceptEncoding(value)) } - /// Returns the most prefered encoding that is specified by the client, + /// Returns the most prefered encoding that is specified by the header, /// if one is specified. /// /// Note: This peeks at the underlying iter, not modifying it. diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index 4c091131..c13c1bd7 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -6,6 +6,17 @@ use itertools::Itertools; use util::{FlatCsv, TryFromValues}; use HeaderValue; +/// A CSV list that respects the Quality Values syntax defined in +/// [RFC7321](https://tools.ietf.org/html/rfc7231#section-5.3.1) +/// +/// Many of the request header fields for proactive negotiation use a +/// common parameter, named "q" (case-insensitive), to assign a relative +/// "weight" to the preference for that associated kind of content. This +/// weight is referred to as a "quality value" (or "qvalue") because the +/// same parameter name is often used within server configurations to +/// assign a weight to the relative quality of the various +/// representations that can be selected for a resource. +/// #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct QualityValue { csv: FlatCsv, From 23c78fa28b8f1a39aa7b9acd46fd9c9433fb6af9 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 22 Mar 2020 22:41:23 -0400 Subject: [PATCH 05/57] making the QualityValue in AcceptEncoding public, so an AcceptEncoding header can be created from HeaderValue, moving the implementation details of QualityValue into a sealed mod --- src/common/accept_encoding.rs | 9 +- src/util/quality_value.rs | 222 ++++++++++++++++++---------------- 2 files changed, 121 insertions(+), 110 deletions(-) diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs index 5c5bf3b2..00495e0a 100644 --- a/src/common/accept_encoding.rs +++ b/src/common/accept_encoding.rs @@ -23,7 +23,7 @@ use HeaderValue; /// * `br;q=1.0, gzip;q=0.8` /// #[derive(Clone, Debug)] -pub struct AcceptEncoding(QualityValue); +pub struct AcceptEncoding(pub QualityValue); derive_header! { AcceptEncoding(_), @@ -87,12 +87,13 @@ impl AcceptEncoding { /// ``` /// use headers::{AcceptEncoding, HeaderValue}; /// - /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; - /// let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); + /// let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.8"); + /// let accept_enc = AcceptEncoding(val.into()); /// let mut encodings = accept_enc.sorted_encodings(); /// - /// assert_eq!(encodings.next(), Some("gzip")); /// assert_eq!(encodings.next(), Some("deflate")); + /// assert_eq!(encodings.next(), Some("gzip")); + /// assert_eq!(encodings.next(), Some("br")); /// assert_eq!(encodings.next(), None); /// ``` pub fn sorted_encodings(&self) -> impl Iterator { diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index c13c1bd7..abb0362d 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -1,14 +1,10 @@ -use std::cmp::Ordering; -use std::convert::{From, TryFrom}; +use self::sealed::SemiQ; use std::marker::PhantomData; - -use itertools::Itertools; -use util::{FlatCsv, TryFromValues}; -use HeaderValue; +use util::FlatCsv; /// A CSV list that respects the Quality Values syntax defined in /// [RFC7321](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// +/// /// Many of the request header fields for proactive negotiation use a /// common parameter, named "q" (case-insensitive), to assign a relative /// "weight" to the preference for that associated kind of content. This @@ -16,148 +12,162 @@ use HeaderValue; /// same parameter name is often used within server configurations to /// assign a weight to the relative quality of the various /// representations that can be selected for a resource. -/// +/// #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) struct QualityValue { +pub struct QualityValue { csv: FlatCsv, _marker: PhantomData, } -pub(crate) trait QualityDelimiter { - const STR: &'static str; -} +mod sealed { + use super::QualityValue; + use std::cmp::Ordering; + use std::convert::{From, TryFrom}; + use std::marker::PhantomData; -/// enum that represents the ';q=' delimiter -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub(crate) enum SemiQ {} + use itertools::Itertools; + use util::{FlatCsv, TryFromValues}; + use HeaderValue; -impl QualityDelimiter for SemiQ { - const STR: &'static str = ";q="; -} + pub trait QualityDelimiter { + const STR: &'static str; + } -/// enum that represents the ';level=' delimiter (extremely rare) -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub(crate) enum SemiLevel {} + /// enum that represents the ';q=' delimiter + #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + pub enum SemiQ {} -impl QualityDelimiter for SemiLevel { - const STR: &'static str = ";level="; -} + impl QualityDelimiter for SemiQ { + const STR: &'static str = ";q="; + } -#[derive(Clone, Debug, PartialEq, Eq)] -struct QualityMeta<'a, Sep = SemiQ> { - pub data: &'a str, - pub quality: u16, - _marker: PhantomData, -} + /// enum that represents the ';level=' delimiter (extremely rare) + #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + pub enum SemiLevel {} -impl Ord for QualityMeta<'_, Delm> { - fn cmp(&self, other: &Self) -> Ordering { - other.quality.cmp(&self.quality) + impl QualityDelimiter for SemiLevel { + const STR: &'static str = ";level="; } -} -impl PartialOrd for QualityMeta<'_, Delm> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + #[derive(Clone, Debug, PartialEq, Eq)] + struct QualityMeta<'a, Sep = SemiQ> { + pub data: &'a str, + pub quality: u16, + _marker: PhantomData, } -} - -impl<'a, Delm: QualityDelimiter> TryFrom<&'a str> for QualityMeta<'a, Delm> { - type Error = ::Error; - fn try_from(val: &'a str) -> Result { - let mut parts: Vec<&str> = val.split(Delm::STR).collect(); + impl Ord for QualityMeta<'_, Delm> { + fn cmp(&self, other: &Self) -> Ordering { + other.quality.cmp(&self.quality) + } + } - match (parts.pop(), parts.pop()) { - (Some(qual), Some(data)) => { - let parsed: f32 = qual.parse().map_err(|_| ::Error::invalid())?; - let quality = (parsed * 1000_f32) as u16; + impl PartialOrd for QualityMeta<'_, Delm> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } - Ok(QualityMeta { + impl<'a, Delm: QualityDelimiter> TryFrom<&'a str> for QualityMeta<'a, Delm> { + type Error = ::Error; + + fn try_from(val: &'a str) -> Result { + let mut parts: Vec<&str> = val.split(Delm::STR).collect(); + + match (parts.pop(), parts.pop()) { + (Some(qual), Some(data)) => { + let parsed: f32 = qual.parse().map_err(|_| ::Error::invalid())?; + let quality = (parsed * 1000_f32) as u16; + + Ok(QualityMeta { + data, + quality, + _marker: PhantomData, + }) + } + // No deliter present, assign a quality value of 1 + (Some(data), None) => Ok(QualityMeta { data, - quality, + quality: 1000_u16, _marker: PhantomData, - }) + }), + _ => Err(::Error::invalid()), } - // No deliter present, assign a quality value of 1 - (Some(data), None) => Ok(QualityMeta { - data, - quality: 1000_u16, - _marker: PhantomData, - }), - _ => Err(::Error::invalid()), } } -} -impl QualityValue { - pub(crate) fn iter(&self) -> impl Iterator { - self.csv - .iter() - .map(|v| QualityMeta::::try_from(v).unwrap()) - .into_iter() - .sorted() - .map(|pair| pair.data) - .into_iter() + impl QualityValue { + pub(crate) fn iter(&self) -> impl Iterator { + self.csv + .iter() + .map(|v| QualityMeta::::try_from(v).unwrap()) + .into_iter() + .sorted() + .map(|pair| pair.data) + .into_iter() + } } -} -impl From for QualityValue { - fn from(csv: FlatCsv) -> Self { - QualityValue { - csv, - _marker: PhantomData, + impl From for QualityValue { + fn from(csv: FlatCsv) -> Self { + QualityValue { + csv, + _marker: PhantomData, + } } } -} -impl> TryFrom<(&str, F)> for QualityValue { - type Error = ::Error; + impl> TryFrom<(&str, F)> for QualityValue { + type Error = ::Error; - fn try_from(pair: (&str, F)) -> Result { - let value = HeaderValue::try_from(format!("{}{}{}", pair.0, Delm::STR, pair.1.into())) - .map_err(|_e| ::Error::invalid())?; - Ok(QualityValue { - csv: value.into(), - _marker: PhantomData, - }) + fn try_from(pair: (&str, F)) -> Result { + let value = HeaderValue::try_from(format!("{}{}{}", pair.0, Delm::STR, pair.1.into())) + .map_err(|_e| ::Error::invalid())?; + Ok(QualityValue { + csv: value.into(), + _marker: PhantomData, + }) + } } -} -impl From for QualityValue { - fn from(value: HeaderValue) -> Self { - QualityValue { - csv: value.into(), - _marker: PhantomData, + impl From for QualityValue { + fn from(value: HeaderValue) -> Self { + QualityValue { + csv: value.into(), + _marker: PhantomData, + } } } -} -impl<'a, Delm> From<&'a QualityValue> for HeaderValue { - fn from(qual: &'a QualityValue) -> HeaderValue { - qual.csv.value.clone() + impl<'a, Delm> From<&'a QualityValue> for HeaderValue { + fn from(qual: &'a QualityValue) -> HeaderValue { + qual.csv.value.clone() + } } -} -impl From> for HeaderValue { - fn from(qual: QualityValue) -> HeaderValue { - qual.csv.value + impl From> for HeaderValue { + fn from(qual: QualityValue) -> HeaderValue { + qual.csv.value + } } -} -impl TryFromValues for QualityValue { - fn try_from_values<'i, I>(values: &mut I) -> Result - where - I: Iterator, - { - let flat: FlatCsv = values.collect(); - Ok(QualityValue::from(flat)) + impl TryFromValues for QualityValue { + fn try_from_values<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + let flat: FlatCsv = values.collect(); + Ok(QualityValue::from(flat)) + } } } #[cfg(test)] mod tests { - use super::*; + use super::{ + sealed::{SemiLevel, SemiQ}, + QualityValue, + }; use HeaderValue; #[test] From 2fe75b4270837ec49790e9ba9d77fdb3ff346480 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 29 Mar 2020 16:21:26 -0400 Subject: [PATCH 06/57] creating a ContentCoding type defined via a macro --- src/common/content_coding.rs | 78 ++++++++++++++++++++++++++++++++++ src/common/content_encoding.rs | 51 ++++++++++------------ src/common/mod.rs | 2 + 3 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 src/common/content_coding.rs diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs new file mode 100644 index 00000000..20d00bc2 --- /dev/null +++ b/src/common/content_coding.rs @@ -0,0 +1,78 @@ +macro_rules! define_content_coding { + ($($coding:ident; $str:expr,)+) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + /// Values that are used with headers like `Content-Encoding` + /// [RFC7231](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) + /// + pub enum ContentCoding { + $( + #[doc = $str] + $coding, + )+ + } + + impl ContentCoding { + /// Returns a static str for a ContentCoding + #[inline] + pub fn to_static(&self) -> &'static str { + match *self { + $(ContentCoding::$coding => $str,)+ + } + } + } + + impl std::string::ToString for ContentCoding { + #[inline] + fn to_string(&self) -> String { + match *self { + $(ContentCoding::$coding => $str.to_string(),)+ + } + } + } + + impl std::str::FromStr for ContentCoding { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + $( + stringify!($coding) + | $str => Ok(ContentCoding::$coding), + )+ + _ => Err("invalid content coding") + } + } + } + } +} + +define_content_coding! { + BROTLI; "br", + COMPRESS; "compress", + DEFLATE; "deflate", + GZIP; "gzip", + IDENTITY; "identity", +} + +#[cfg(test)] +mod tests { + use super::ContentCoding; + use std::str::FromStr; + + #[test] + fn to_static() { + assert_eq!(ContentCoding::GZIP.to_static(), "gzip"); + } + + #[test] + fn to_string() { + assert_eq!(ContentCoding::DEFLATE.to_string(), "deflate".to_string()); + } + + #[test] + fn from_str() { + assert_eq!(ContentCoding::from_str("br"), Ok(ContentCoding::BROTLI)); + assert_eq!(ContentCoding::from_str("GZIP"), Ok(ContentCoding::GZIP)); + assert_eq!(ContentCoding::from_str("blah blah"), Err("invalid content coding")); + } +} \ No newline at end of file diff --git a/src/common/content_encoding.rs b/src/common/content_encoding.rs index e50be4bb..73d23d24 100644 --- a/src/common/content_encoding.rs +++ b/src/common/content_encoding.rs @@ -39,35 +39,30 @@ derive_header! { name: CONTENT_ENCODING } -impl ContentEncoding { - /// A constructor to easily create a `Content-Encoding: gzip` header. - #[inline] - pub fn gzip() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static("gzip").into()) - } - - /// A constructor to easily create a `Content-Encoding: compress` header. - #[inline] - pub fn compress() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static("compress").into()) - } - - /// A constructor to easily create a `Content-Encoding: deflate` header. - #[inline] - pub fn deflate() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static("deflate").into()) - } - - /// A constructor to easily create a `Content-Encoding: identity` header. - #[inline] - pub fn identity() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static("identity").into()) - } +macro_rules! derive_constructor { + ($(doc = $doc:expr; $coding:ident,)+) => { + $( + #[doc = $doc] + #[inline] + pub fn $coding() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static(stringify!($coding)).into()) + } + )+ + }; +} - /// A constructor to easily create a `Content-Encoding: br` header. - #[inline] - pub fn br() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static("br").into()) +impl ContentEncoding { + derive_constructor! { + doc = "A constructor to easily create a `Content-Encoding`: gzip header"; + gzip, + doc = "A constructor to easily create a `Content-Encoding`: compress header"; + compress, + doc = "A constructor to easily create a `Content-Encoding`: deflate header"; + deflate, + doc = "A constructor to easily create a `Content-Encoding`: identity header"; + identity, + doc = "A constructor to easily create a `Content-Encoding`: br header"; + br, } /// Check if this header contains a given "coding". diff --git a/src/common/mod.rs b/src/common/mod.rs index db6234b0..c17cd74b 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -23,6 +23,7 @@ pub use self::allow::Allow; pub use self::authorization::Authorization; pub use self::cache_control::CacheControl; pub use self::connection::Connection; +pub use self::content_coding::ContentCoding; pub use self::content_disposition::ContentDisposition; pub use self::content_encoding::ContentEncoding; //pub use self::content_language::ContentLanguage; @@ -142,6 +143,7 @@ mod allow; pub mod authorization; mod cache_control; mod connection; +mod content_coding; mod content_disposition; mod content_encoding; //mod content_language; From 56bed20544861665a1ce2bf0ab89e65fd0003304 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 29 Mar 2020 23:29:41 -0400 Subject: [PATCH 07/57] adapting accept encoding to use content coding --- src/common/accept_encoding.rs | 53 +++++++++++++++++-------- src/common/content_coding.rs | 74 +++++++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs index 00495e0a..245054cc 100644 --- a/src/common/accept_encoding.rs +++ b/src/common/accept_encoding.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; + +use {ContentCoding, HeaderValue}; use util::{QualityValue, TryFromValues}; -use HeaderValue; /// `Accept-Encoding` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.4) @@ -68,35 +69,55 @@ impl AcceptEncoding { /// # Example /// /// ``` - /// use headers::AcceptEncoding; + /// use headers::{AcceptEncoding, ContentCoding}; /// /// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)]; /// let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); /// let mut encodings = accept_enc.sorted_encodings(); /// - /// assert_eq!(accept_enc.prefered_encoding(), Some("gzip")); + /// assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::GZIP)); /// ``` - pub fn prefered_encoding(&self) -> Option<&str> { - self.0.iter().peekable().peek().map(|s| *s) + pub fn prefered_encoding(&self) -> Option { + self.0.iter().peekable().peek().map(|s| ContentCoding::from_str(*s)) } - /// Returns a quality sorted iterator of the accepted encodings + /// Returns a quality sorted iterator of the `ContentCoding` /// /// # Example /// /// ``` - /// use headers::{AcceptEncoding, HeaderValue}; + /// use headers::{AcceptEncoding, ContentCoding, HeaderValue}; /// /// let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.8"); /// let accept_enc = AcceptEncoding(val.into()); /// let mut encodings = accept_enc.sorted_encodings(); /// + /// assert_eq!(encodings.next(), Some(ContentCoding::DEFLATE)); + /// assert_eq!(encodings.next(), Some(ContentCoding::GZIP)); + /// assert_eq!(encodings.next(), Some(ContentCoding::BROTLI)); + /// assert_eq!(encodings.next(), None); + /// ``` + pub fn sorted_encodings<'a>(&'a self) -> impl Iterator + 'a { + self.0.iter().map(|s| ContentCoding::from_str(s)) + } + + /// Returns a quality sorted iterator of values + /// + /// # Example + /// + /// ``` + /// use headers::{AcceptEncoding, ContentCoding, HeaderValue}; + /// + /// let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.8"); + /// let accept_enc = AcceptEncoding(val.into()); + /// let mut encodings = accept_enc.sorted_values(); + /// /// assert_eq!(encodings.next(), Some("deflate")); /// assert_eq!(encodings.next(), Some("gzip")); /// assert_eq!(encodings.next(), Some("br")); /// assert_eq!(encodings.next(), None); /// ``` - pub fn sorted_encodings(&self) -> impl Iterator { + pub fn sorted_values(&self) -> impl Iterator { self.0.iter() } } @@ -104,19 +125,19 @@ impl AcceptEncoding { #[cfg(test)] mod tests { use super::*; - use HeaderValue; + use {ContentCoding, HeaderValue}; #[test] fn from_static() { let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.9"); let accept_enc = AcceptEncoding(val.into()); - assert_eq!(accept_enc.prefered_encoding(), Some("deflate")); + assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::DEFLATE)); let mut encodings = accept_enc.sorted_encodings(); - assert_eq!(encodings.next(), Some("deflate")); - assert_eq!(encodings.next(), Some("gzip")); - assert_eq!(encodings.next(), Some("br")); + assert_eq!(encodings.next(), Some(ContentCoding::DEFLATE)); + assert_eq!(encodings.next(), Some(ContentCoding::GZIP)); + assert_eq!(encodings.next(), Some(ContentCoding::BROTLI)); assert_eq!(encodings.next(), None); } @@ -125,11 +146,11 @@ mod tests { let pairs = vec![("gzip", 1.0), ("br", 0.9)]; let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap(); - assert_eq!(accept_enc.prefered_encoding(), Some("gzip")); + assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::GZIP)); let mut encodings = accept_enc.sorted_encodings(); - assert_eq!(encodings.next(), Some("gzip")); - assert_eq!(encodings.next(), Some("br")); + assert_eq!(encodings.next(), Some(ContentCoding::GZIP)); + assert_eq!(encodings.next(), Some(ContentCoding::BROTLI)); assert_eq!(encodings.next(), None); } } diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs index 20d00bc2..0137488a 100644 --- a/src/common/content_coding.rs +++ b/src/common/content_coding.rs @@ -19,27 +19,64 @@ macro_rules! define_content_coding { $(ContentCoding::$coding => $str,)+ } } - } - impl std::string::ToString for ContentCoding { + /// Given a &str returns a ContentCoding. + /// + /// Note this will never fail, in the case of `&str` being an invalid content coding, + /// will return `ContentCoding::IDENTITY` because identity is generally always an + /// accepted coding. + /// + /// # Example + /// + /// ``` + /// use headers::ContentCoding; + /// + /// let invalid = ContentCoding::from_str("not a valid coding"); + /// assert_eq!(invalid, ContentCoding::IDENTITY); + /// + /// let valid = ContentCoding::from_str("gzip"); + /// assert_eq!(valid, ContentCoding::GZIP); + /// ``` + /// #[inline] - fn to_string(&self) -> String { - match *self { - $(ContentCoding::$coding => $str.to_string(),)+ - } + pub fn from_str(s: &str) -> Self { + ContentCoding::try_from_str(s).unwrap_or_else(|_| ContentCoding::IDENTITY) } - } - - impl std::str::FromStr for ContentCoding { - type Err = &'static str; - fn from_str(s: &str) -> Result { + #[inline] + /// Given a &str will try to return a ContentCoding + /// + /// Different from `ContentCoding::from_str(&str)`, if `&str` is an invalid content + /// coding, it will return `Err(())` + /// + /// # Example + /// + /// ``` + /// use headers::ContentCoding; + /// + /// let invalid = ContentCoding::try_from_str("not a valid coding"); + /// assert!(invalid.is_err()); + /// + /// let valid = ContentCoding::try_from_str("gzip"); + /// assert_eq!(valid.unwrap(), ContentCoding::GZIP); + /// ``` + /// + pub fn try_from_str(s: &str) -> Result { match s { $( stringify!($coding) | $str => Ok(ContentCoding::$coding), )+ - _ => Err("invalid content coding") + _ => Err(()) + } + } + } + + impl std::string::ToString for ContentCoding { + #[inline] + fn to_string(&self) -> String { + match *self { + $(ContentCoding::$coding => $str.to_string(),)+ } } } @@ -57,7 +94,6 @@ define_content_coding! { #[cfg(test)] mod tests { use super::ContentCoding; - use std::str::FromStr; #[test] fn to_static() { @@ -71,8 +107,14 @@ mod tests { #[test] fn from_str() { - assert_eq!(ContentCoding::from_str("br"), Ok(ContentCoding::BROTLI)); - assert_eq!(ContentCoding::from_str("GZIP"), Ok(ContentCoding::GZIP)); - assert_eq!(ContentCoding::from_str("blah blah"), Err("invalid content coding")); + assert_eq!(ContentCoding::from_str("br"), ContentCoding::BROTLI); + assert_eq!(ContentCoding::from_str("GZIP"), ContentCoding::GZIP); + assert_eq!(ContentCoding::from_str("blah blah"), ContentCoding::IDENTITY); + } + + #[test] + fn try_from_str() { + assert_eq!(ContentCoding::try_from_str("br"), Ok(ContentCoding::BROTLI)); + assert_eq!(ContentCoding::try_from_str("blah blah"), Err(())); } } \ No newline at end of file From 5eeb66fb40c802ba4bf1a3bb3d16789c64f84f74 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 29 Mar 2020 23:38:52 -0400 Subject: [PATCH 08/57] impl From for HeaderValue --- src/common/content_coding.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs index 0137488a..30d1a8f7 100644 --- a/src/common/content_coding.rs +++ b/src/common/content_coding.rs @@ -1,3 +1,5 @@ +use HeaderValue; + macro_rules! define_content_coding { ($($coding:ident; $str:expr,)+) => { #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -80,6 +82,14 @@ macro_rules! define_content_coding { } } } + + impl From for HeaderValue { + fn from(coding: ContentCoding) -> HeaderValue { + match coding { + $(ContentCoding::$coding => HeaderValue::from_static($str),)+ + } + } + } } } From fd8f22f710299ff1523597288afdb876703b0205 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Mon, 30 Mar 2020 00:55:50 -0400 Subject: [PATCH 09/57] cleaning up comments of ContentCoding and removing changes to ContentEncoding --- src/common/content_coding.rs | 27 ++++++++++++++++++--------- src/common/content_encoding.rs | 27 ++++----------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs index 30d1a8f7..777b414d 100644 --- a/src/common/content_coding.rs +++ b/src/common/content_coding.rs @@ -1,11 +1,13 @@ use HeaderValue; +// Derives an enum to represent content codings and some helpful impls macro_rules! define_content_coding { ($($coding:ident; $str:expr,)+) => { #[derive(Copy, Clone, Debug, Eq, PartialEq)] - /// Values that are used with headers like `Content-Encoding` - /// [RFC7231](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) + /// Values that are used with headers like [`Content-Encoding`](self::ContentEncoding) or + /// [`Accept-Encoding`](self::AcceptEncoding) /// + /// [RFC7231](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) pub enum ContentCoding { $( #[doc = $str] @@ -14,7 +16,16 @@ macro_rules! define_content_coding { } impl ContentCoding { - /// Returns a static str for a ContentCoding + /// Returns a `&'static str` for a `ContentCoding` + /// + /// # Example + /// + /// ``` + /// use headers::ContentCoding; + /// + /// let coding = ContentCoding::BROTLI; + /// assert_eq!(coding.to_static(), "br"); + /// ``` #[inline] pub fn to_static(&self) -> &'static str { match *self { @@ -22,10 +33,10 @@ macro_rules! define_content_coding { } } - /// Given a &str returns a ContentCoding. + /// Given a `&str` returns a `ContentCoding` /// /// Note this will never fail, in the case of `&str` being an invalid content coding, - /// will return `ContentCoding::IDENTITY` because identity is generally always an + /// will return `ContentCoding::IDENTITY` because `'identity'` is generally always an /// accepted coding. /// /// # Example @@ -39,14 +50,12 @@ macro_rules! define_content_coding { /// let valid = ContentCoding::from_str("gzip"); /// assert_eq!(valid, ContentCoding::GZIP); /// ``` - /// #[inline] pub fn from_str(s: &str) -> Self { ContentCoding::try_from_str(s).unwrap_or_else(|_| ContentCoding::IDENTITY) } - #[inline] - /// Given a &str will try to return a ContentCoding + /// Given a `&str` will try to return a `ContentCoding` /// /// Different from `ContentCoding::from_str(&str)`, if `&str` is an invalid content /// coding, it will return `Err(())` @@ -62,7 +71,7 @@ macro_rules! define_content_coding { /// let valid = ContentCoding::try_from_str("gzip"); /// assert_eq!(valid.unwrap(), ContentCoding::GZIP); /// ``` - /// + #[inline] pub fn try_from_str(s: &str) -> Result { match s { $( diff --git a/src/common/content_encoding.rs b/src/common/content_encoding.rs index 73d23d24..444eb41c 100644 --- a/src/common/content_encoding.rs +++ b/src/common/content_encoding.rs @@ -39,30 +39,11 @@ derive_header! { name: CONTENT_ENCODING } -macro_rules! derive_constructor { - ($(doc = $doc:expr; $coding:ident,)+) => { - $( - #[doc = $doc] - #[inline] - pub fn $coding() -> ContentEncoding { - ContentEncoding(HeaderValue::from_static(stringify!($coding)).into()) - } - )+ - }; -} - impl ContentEncoding { - derive_constructor! { - doc = "A constructor to easily create a `Content-Encoding`: gzip header"; - gzip, - doc = "A constructor to easily create a `Content-Encoding`: compress header"; - compress, - doc = "A constructor to easily create a `Content-Encoding`: deflate header"; - deflate, - doc = "A constructor to easily create a `Content-Encoding`: identity header"; - identity, - doc = "A constructor to easily create a `Content-Encoding`: br header"; - br, + /// A constructor to easily create a `Content-Encoding: gzip` header. + #[inline] + pub fn gzip() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("gzip").into()) } /// Check if this header contains a given "coding". From d6cfefbf04f8c0fc526943fde7fb58627ab1358a Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Wed, 11 Nov 2020 13:53:55 +0100 Subject: [PATCH 10/57] Bump base64 to 0.13 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a88f18e7..4033bd1c 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.12" +base64 = "0.13" bitflags = "1.0" bytes = "0.5" mime = "0.3.14" From 028c48f417c4df7dbe5471e4dd610c3d0a69fab4 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Wed, 23 Dec 2020 21:47:07 +0100 Subject: [PATCH 11/57] Bump bytes to 1.0 (#78) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4033bd1c..b8bf88f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ http = "0.2.0" headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.13" bitflags = "1.0" -bytes = "0.5" +bytes = "1" mime = "0.3.14" sha-1 = "0.8" time = "0.1.34" From 919c380f9c26bd2341e0171f6114c1b696ff2df0 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Fri, 8 Jan 2021 22:17:47 +0100 Subject: [PATCH 12/57] Escape [sic] in docs (not an intra doc link) (#79) --- src/common/referer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/referer.rs b/src/common/referer.rs index 864cbd72..c85973b9 100644 --- a/src/common/referer.rs +++ b/src/common/referer.rs @@ -5,7 +5,7 @@ use http::header::HeaderValue; /// `Referer` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2) /// -/// The `Referer` [sic] header field allows the user agent to specify a +/// The `Referer` \[sic\] header field allows the user agent to specify a /// URI reference for the resource from which the target URI was obtained /// (i.e., the "referrer", though the field name is misspelled). A user /// agent MUST NOT include the fragment and userinfo components of the From c1e111a65f669fc71c3461bf214083652f72fded Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 13 Jan 2021 13:35:53 -0800 Subject: [PATCH 13/57] headers:v0.3.3 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8bf88f6..1d8d75f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.2" # don't forget to update html_root_url +version = "0.3.3" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index bf05e9fa..ed0db108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.2")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.3")] //! # Typed HTTP Headers //! From 9762ea048fde589527c9cc54ddbbad25d2d8256d Mon Sep 17 00:00:00 2001 From: Igor Raits Date: Fri, 5 Feb 2021 15:24:46 +0100 Subject: [PATCH 14/57] chore: Update sha-1 to 0.9 (#72) Signed-off-by: Igor Raits --- .travis.yml | 2 +- Cargo.toml | 2 +- src/common/sec_websocket_accept.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89f5bfcc..20a84338 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: - rust: beta - rust: nightly # minimum rustc version - - rust: 1.39.0 + - rust: 1.41.0 script: cargo build script: diff --git a/Cargo.toml b/Cargo.toml index 1d8d75f4..52613d28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ base64 = "0.13" bitflags = "1.0" bytes = "1" mime = "0.3.14" -sha-1 = "0.8" +sha-1 = "0.9" time = "0.1.34" [features] diff --git a/src/common/sec_websocket_accept.rs b/src/common/sec_websocket_accept.rs index b5152479..9e9176f0 100644 --- a/src/common/sec_websocket_accept.rs +++ b/src/common/sec_websocket_accept.rs @@ -37,9 +37,9 @@ impl From for SecWebsocketAccept { fn sign(key: &[u8]) -> SecWebsocketAccept { let mut sha1 = Sha1::default(); - sha1.input(key); - sha1.input(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); - let b64 = Bytes::from(base64::encode(&sha1.result())); + sha1.update(key); + sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); + let b64 = Bytes::from(base64::encode(&sha1.finalize())); let val = ::HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value"); From f8ae7624bb7989bc1ed2a05124fb461b567b8b58 Mon Sep 17 00:00:00 2001 From: nickelc Date: Tue, 16 Feb 2021 23:03:54 +0100 Subject: [PATCH 15/57] Migrate to GitHub Actions (#81) * Migrate to GitHub Actions * use actions-rs/* * add "required" parameters --- .github/workflows/ci.yml | 59 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 21 -------------- README.md | 2 +- 3 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d3ab3cb5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: [push, pull_request] + +env: + minrust: 1.41.0 + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + strategy: + matrix: + rust: [stable, beta, nightly] + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + 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 + + - name: Check minimal versions + if: matrix.rust == 'nightly' + run: | + cargo clean + cargo update -Z minimal-versions + cargo check + + MSRV: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install rust ${{ env.minrust }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.minrust }} + profile: minimal + override: true + + - name: cargo build + uses: actions-rs/cargo@v1 + with: + command: build diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 20a84338..00000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: rust -sudo: false - -cache: cargo - -matrix: - include: - - rust: stable - - rust: beta - - rust: nightly - # minimum rustc version - - rust: 1.41.0 - script: cargo build - -script: - - cargo test --all - - 'if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo test --benches; fi' - -notifications: - email: - on_success: never diff --git a/README.md b/README.md index d68f3df9..a93368fb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # rust http headers -[![Build Status](https://travis-ci.org/hyperium/headers.svg?branch=master)](https://travis-ci.org/hyperium/header) +[![Build Status](https://github.com/hyperium/headers/workflows/CI/badge.svg)](https://github.com/hyperium/headers/actions?query=workflow%3ACI) Typed HTTP headers. From c20737231dc7a1cdcd787dc3249aaa0992244881 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 3 Mar 2021 11:03:17 -0800 Subject: [PATCH 16/57] headers:v0.3.4 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52613d28..0fbe9b99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.3" # don't forget to update html_root_url +version = "0.3.4" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index ed0db108..aaeab3ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.3")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.4")] //! # Typed HTTP Headers //! From c6a8a637a7bb5d56d04311c61611aa66c2703bcb Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Thu, 16 Sep 2021 00:06:31 -0400 Subject: [PATCH 17/57] add try_from for AccessControlAllowOrigin --- src/common/access_control_allow_origin.rs | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/common/access_control_allow_origin.rs b/src/common/access_control_allow_origin.rs index c048bf83..ee4bd8cc 100644 --- a/src/common/access_control_allow_origin.rs +++ b/src/common/access_control_allow_origin.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use super::origin::Origin; use util::{IterExt, TryFromValues}; use HeaderValue; @@ -25,9 +27,11 @@ use HeaderValue; /// ``` /// # extern crate headers; /// use headers::AccessControlAllowOrigin; +/// use std::convert::TryFrom; /// /// let any_origin = AccessControlAllowOrigin::ANY; /// let null_origin = AccessControlAllowOrigin::NULL; +/// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000".to_string()); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AccessControlAllowOrigin(OriginOrAny); @@ -60,6 +64,16 @@ impl AccessControlAllowOrigin { } } +impl TryFrom for AccessControlAllowOrigin { + type Error = ::Error; + + fn try_from(s: String) -> Result { + let header_value = HeaderValue::from_str(&s).map_err(|_| headers_core::Error::invalid())?; + let origin = OriginOrAny::try_from_values(&mut vec![header_value].iter())?; + Ok(Self(origin)) + } +} + impl TryFromValues for OriginOrAny { fn try_from_values<'i, I>(values: &mut I) -> Result where @@ -89,12 +103,14 @@ impl<'a> From<&'a OriginOrAny> for HeaderValue { #[cfg(test)] mod tests { + use super::super::{test_decode, test_encode}; use super::*; #[test] fn origin() { let s = "http://web-platform.test:8000"; + let allow_origin = test_decode::(&[s]).unwrap(); { let origin = allow_origin.origin().unwrap(); @@ -107,6 +123,22 @@ mod tests { assert_eq!(headers["access-control-allow-origin"], s); } + #[test] + fn try_from_origin() { + let s = "http://web-platform.test:8000"; + + let allow_origin = AccessControlAllowOrigin::try_from(s.to_string()).unwrap(); + { + let origin = allow_origin.origin().unwrap(); + assert_eq!(origin.scheme(), "http"); + assert_eq!(origin.hostname(), "web-platform.test"); + assert_eq!(origin.port(), Some(8000)); + } + + let headers = test_encode(allow_origin); + assert_eq!(headers["access-control-allow-origin"], s); + } + #[test] fn any() { let allow_origin = test_decode::(&["*"]).unwrap(); From 84b453463c20eb8276bd3791de0e8b165a5b422a Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Thu, 16 Sep 2021 00:17:50 -0400 Subject: [PATCH 18/57] tryfrom HeaderValue -> OriginOrAny --- src/common/access_control_allow_origin.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/common/access_control_allow_origin.rs b/src/common/access_control_allow_origin.rs index ee4bd8cc..8d38bf17 100644 --- a/src/common/access_control_allow_origin.rs +++ b/src/common/access_control_allow_origin.rs @@ -31,7 +31,7 @@ use HeaderValue; /// /// let any_origin = AccessControlAllowOrigin::ANY; /// let null_origin = AccessControlAllowOrigin::NULL; -/// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000".to_string()); +/// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000"); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AccessControlAllowOrigin(OriginOrAny); @@ -64,16 +64,26 @@ impl AccessControlAllowOrigin { } } -impl TryFrom for AccessControlAllowOrigin { +impl TryFrom<&str> for AccessControlAllowOrigin { type Error = ::Error; - fn try_from(s: String) -> Result { - let header_value = HeaderValue::from_str(&s).map_err(|_| headers_core::Error::invalid())?; - let origin = OriginOrAny::try_from_values(&mut vec![header_value].iter())?; + fn try_from(s: &str) -> Result { + let header_value = HeaderValue::from_str(s).map_err(|_| ::Error::invalid())?; + let origin = OriginOrAny::try_from(&header_value)?; Ok(Self(origin)) } } +impl TryFrom<&HeaderValue> for OriginOrAny { + type Error = ::Error; + + fn try_from(header_value: &HeaderValue) -> Result { + Origin::try_from_value(header_value) + .map(OriginOrAny::Origin) + .ok_or_else(::Error::invalid) + } +} + impl TryFromValues for OriginOrAny { fn try_from_values<'i, I>(values: &mut I) -> Result where @@ -127,7 +137,7 @@ mod tests { fn try_from_origin() { let s = "http://web-platform.test:8000"; - let allow_origin = AccessControlAllowOrigin::try_from(s.to_string()).unwrap(); + let allow_origin = AccessControlAllowOrigin::try_from(s).unwrap(); { let origin = allow_origin.origin().unwrap(); assert_eq!(origin.scheme(), "http"); From 2d9a5c44f0eb7308c49845d00501f55278d2d893 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Thu, 16 Sep 2021 17:06:16 -0400 Subject: [PATCH 19/57] move MSRV to 1.46 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3ab3cb5..a2140ace 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] env: - minrust: 1.41.0 + minrust: 1.46.0 jobs: test: From 4bf199f38d1403bb2cfa10c1644fd7e218faf281 Mon Sep 17 00:00:00 2001 From: Brandon W Maister Date: Fri, 26 Mar 2021 12:06:29 -0400 Subject: [PATCH 20/57] Replace `time` with `httpdate` --- Cargo.toml | 2 +- src/lib.rs | 2 +- src/util/http_date.rs | 76 +++++++++++++++---------------------------- 3 files changed, 29 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0fbe9b99..5086af87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ bitflags = "1.0" bytes = "1" mime = "0.3.14" sha-1 = "0.9" -time = "0.1.34" +httpdate = "1" [features] nightly = [] diff --git a/src/lib.rs b/src/lib.rs index aaeab3ad..0c3fa7a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,11 +78,11 @@ extern crate bitflags; extern crate bytes; extern crate headers_core; extern crate http; +extern crate httpdate; extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))] extern crate test; -extern crate time; pub use headers_core::{Error, Header}; diff --git a/src/util/http_date.rs b/src/util/http_date.rs index f2d8f7af..da3f8396 100644 --- a/src/util/http_date.rs +++ b/src/util/http_date.rs @@ -1,10 +1,10 @@ use std::fmt; use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::SystemTime; use bytes::Bytes; use http::header::HeaderValue; -use time; +use httpdate; use super::IterExt; @@ -32,7 +32,7 @@ use super::IterExt; // HTTP-date, the sender MUST generate those timestamps in the // IMF-fixdate format. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct HttpDate(time::Tm); +pub(crate) struct HttpDate(httpdate::HttpDate); impl HttpDate { pub(crate) fn from_val(val: &HeaderValue) -> Option { @@ -74,96 +74,74 @@ impl<'a> From<&'a HttpDate> for HeaderValue { impl FromStr for HttpDate { type Err = Error; fn from_str(s: &str) -> Result { - time::strptime(s, "%a, %d %b %Y %T %Z") - .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) - .or_else(|_| time::strptime(s, "%c")) - .map(HttpDate) - .map_err(|_| Error(())) + Ok(HttpDate(s.parse().map_err(|_| Error(()))?)) } } impl fmt::Debug for HttpDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) + fmt::Display::fmt(&self.0, f) } } impl fmt::Display for HttpDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) + fmt::Display::fmt(&self.0, f) } } impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - // subsec nanos always dropped - time::Timespec::new(dur.as_secs() as i64, 0) - } - Err(err) => { - let neg = err.duration(); - // subsec nanos always dropped - time::Timespec::new(-(neg.as_secs() as i64), 0) - } - }; - HttpDate(time::at_utc(tmspec)) + HttpDate(sys.into()) } } impl From for SystemTime { fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } + SystemTime::from(date.0) } } #[cfg(test)] mod tests { use super::HttpDate; - use time::Tm; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); + + use std::time::{Duration, UNIX_EPOCH}; + + // The old tests had Sunday, but 1994-11-07 is a Monday. + // See https://github.com/pyfisch/httpdate/pull/6#issuecomment-846881001 + fn nov_07() -> HttpDate { + HttpDate((UNIX_EPOCH + Duration::new(784198117, 0)).into()) + } + + #[test] + fn test_display_is_imf_fixdate() { + assert_eq!("Mon, 07 Nov 1994 08:48:37 GMT", &nov_07().to_string()); + } #[test] fn test_imf_fixdate() { assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - NOV_07 + "Mon, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), + nov_07() ); } #[test] fn test_rfc_850() { assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" + "Monday, 07-Nov-94 08:48:37 GMT" .parse::() .unwrap(), - NOV_07 + nov_07() ); } #[test] fn test_asctime() { assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 + "Mon Nov 7 08:48:37 1994".parse::().unwrap(), + nov_07() ); } From 4cdea5614ef44f19415dbba38fb0b4efbf13accc Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 18 Oct 2021 12:39:16 -0700 Subject: [PATCH 21/57] headers:v0.3.5 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5086af87..384bfb0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.4" # don't forget to update html_root_url +version = "0.3.5" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 0c3fa7a8..80c9de1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.4")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.5")] //! # Typed HTTP Headers //! From 6b065f9e8c9eebd880f13ae6da08259e4ce79207 Mon Sep 17 00:00:00 2001 From: Vincent Marguerie Date: Wed, 19 Jan 2022 22:27:56 +0100 Subject: [PATCH 22/57] add support for age header --- src/common/age.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++ src/common/mod.rs | 2 ++ src/util/seconds.rs | 6 +++- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/common/age.rs diff --git a/src/common/age.rs b/src/common/age.rs new file mode 100644 index 00000000..11f1f646 --- /dev/null +++ b/src/common/age.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use util::Seconds; + +/// `Age` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.1) +/// +/// The "Age" header field conveys the sender's estimate of the amount of +/// time since the response was generated or successfully validated at +/// the origin server. Age values are calculated as specified in +/// [Section 4.2.3](https://tools.ietf.org/html/rfc7234#section-4.2.3). +/// +/// ## ABNF +/// +/// ```text +/// Age = delta-seconds +/// ``` +/// +/// The Age field-value is a non-negative integer, representing time in +/// seconds (see [Section 1.2.1](https://tools.ietf.org/html/rfc7234#section-1.2.1)). +/// +/// The presence of an Age header field implies that the response was not +/// generated or validated by the origin server for this request. +/// However, lack of an Age header field does not imply the origin was +/// contacted, since the response might have been received from an +/// HTTP/1.0 cache that does not implement Age. +/// +/// ## Example values +/// +/// * `3600` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Age; +/// +/// let len = Age::from_secs(60); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Age(Seconds); + +derive_header! { + Age(_), + name: AGE +} + +impl Age { + /// Creates a new `Age` header from the specified number of whole seconds. + pub fn from_secs(secs: u64) -> Self { + Self(Seconds::from_secs(secs)) + } + + /// Returns the number of seconds for this `Age` header. + pub fn as_secs(&self) -> u64 { + self.0.as_u64() + } +} + +impl From for Age { + fn from(dur: Duration) -> Self { + Age(Seconds::from(dur)) + } +} + +impl From for Duration { + fn from(age: Age) -> Self { + age.0.into() + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a1e9c0f..2237ae8e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -19,6 +19,7 @@ pub use self::access_control_expose_headers::AccessControlExposeHeaders; pub use self::access_control_max_age::AccessControlMaxAge; pub use self::access_control_request_headers::AccessControlRequestHeaders; pub use self::access_control_request_method::AccessControlRequestMethod; +pub use self::age::Age; pub use self::allow::Allow; pub use self::authorization::Authorization; pub use self::cache_control::CacheControl; @@ -138,6 +139,7 @@ mod access_control_expose_headers; mod access_control_max_age; mod access_control_request_headers; mod access_control_request_method; +mod age; mod allow; pub mod authorization; mod cache_control; diff --git a/src/util/seconds.rs b/src/util/seconds.rs index 8cf7b6da..a1a9194b 100644 --- a/src/util/seconds.rs +++ b/src/util/seconds.rs @@ -11,7 +11,11 @@ impl Seconds { pub(crate) fn from_val(val: &HeaderValue) -> Option { let secs = val.to_str().ok()?.parse().ok()?; - Some(Seconds(Duration::from_secs(secs))) + Some(Self::from_secs(secs)) + } + + pub(crate) fn from_secs(secs: u64) -> Self { + Self::from(Duration::from_secs(secs)) } pub(crate) fn as_u64(&self) -> u64 { From 6932af5c6a2c9384c4524febe7d20d87e3d6e837 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 24 Jan 2022 13:18:44 +0100 Subject: [PATCH 23/57] Add forwarding methods to Authorization for provided C's --- src/common/authorization.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/common/authorization.rs b/src/common/authorization.rs index 6442a5fd..dbef1530 100644 --- a/src/common/authorization.rs +++ b/src/common/authorization.rs @@ -45,6 +45,16 @@ impl Authorization { Authorization(Basic { decoded, colon_pos }) } + + /// View the decoded username. + pub fn username(&self) -> &str { + self.0.username() + } + + /// View the decoded password. + pub fn password(&self) -> &str { + self.0.password() + } } impl Authorization { @@ -54,6 +64,11 @@ impl Authorization { .map(|val| Authorization(Bearer(val))) .ok_or_else(|| InvalidBearerToken { _inner: () }) } + + /// View the token part as a `&str`. + pub fn token(&self) -> &str { + self.0.token() + } } impl ::Header for Authorization { From bf815a2def951942b5e303aa4afef3ea95ecd0db Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 28 Jan 2022 16:31:35 -0800 Subject: [PATCH 24/57] headers:v0.3.6 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 384bfb0c..bd2eb1fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.5" # don't forget to update html_root_url +version = "0.3.6" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 80c9de1b..3a80465b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.5")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.6")] //! # Typed HTTP Headers //! From f1dc1e1c4a186ee4fc5c022a6905b295c8f5f03f Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Fri, 10 Dec 2021 17:54:06 -0500 Subject: [PATCH 25/57] Update sha-1 to 0.10.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bd2eb1fe..f99e6d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ base64 = "0.13" bitflags = "1.0" bytes = "1" mime = "0.3.14" -sha-1 = "0.9" +sha-1 = "0.10" httpdate = "1" [features] From ffca4a90482cc31875ac9a9364b7ea252f8c0afa Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 10 Feb 2022 10:03:43 -0800 Subject: [PATCH 26/57] headers:v0.3.7 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f99e6d66..f0d8065e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.6" # don't forget to update html_root_url +version = "0.3.7" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 3a80465b..18dd8c19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.6")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.7")] //! # Typed HTTP Headers //! From 31fe3e19e5763b95c9b5dfed1569bef555380dc4 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Sun, 28 Aug 2022 21:56:38 -0400 Subject: [PATCH 27/57] Switch from `sha-1` to `sha1` The RustCrypto project recently got control of the crate name `sha1`. This commit switches over to the new name; the old `sha-1` name is now deprecated. Details: https://github.com/RustCrypto/hashes/pull/350 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f0d8065e..a93c662c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ base64 = "0.13" bitflags = "1.0" bytes = "1" mime = "0.3.14" -sha-1 = "0.10" +sha1 = "0.10" httpdate = "1" [features] From ffe37f27b49294637a136013fb355b04fb5880df Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 1 Sep 2022 09:20:20 +0200 Subject: [PATCH 28/57] Bump MSRV to 1.51 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2140ace..b3b965d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] env: - minrust: 1.46.0 + minrust: 1.51.0 jobs: test: From 796dce8a4447a5cf0a7f00cfec0605ef2aac8987 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 2 Sep 2022 13:20:00 -0700 Subject: [PATCH 29/57] headers:v0.3.8 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a93c662c..b49c86e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.7" # don't forget to update html_root_url +version = "0.3.8" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 18dd8c19..0d27a810 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.7")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.8")] //! # Typed HTTP Headers //! From aad20040a2100904209c06da12b400fc8a1d0027 Mon Sep 17 00:00:00 2001 From: Bastian Date: Thu, 24 Nov 2022 13:03:02 +0100 Subject: [PATCH 30/57] mark Authorization headers sensitive (#121) Co-authored-by: Bastian Schubert --- src/common/authorization.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/authorization.rs b/src/common/authorization.rs index dbef1530..734bc6e2 100644 --- a/src/common/authorization.rs +++ b/src/common/authorization.rs @@ -94,7 +94,8 @@ impl ::Header for Authorization { } fn encode>(&self, values: &mut E) { - let value = self.0.encode(); + let mut value = self.0.encode(); + value.set_sensitive(true); debug_assert!( value.as_bytes().starts_with(C::SCHEME.as_bytes()), "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", @@ -171,7 +172,8 @@ impl Credentials for Basic { base64::encode_config_buf(&self.decoded, base64::STANDARD, &mut encoded); let bytes = Bytes::from(encoded); - HeaderValue::from_maybe_shared(bytes).expect("base64 encoding is always a valid HeaderValue") + HeaderValue::from_maybe_shared(bytes) + .expect("base64 encoding is always a valid HeaderValue") } } From 92ed9d7c2f281ea7064638b7f598897bfe91432f Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Mon, 20 Feb 2023 10:12:16 -0800 Subject: [PATCH 31/57] 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 32/57] 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 33/57] 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 34/57] 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 33002c471ce4ee94d2f4d47ed85f1b0a62595cfe Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 12 Mar 2023 22:56:27 +0100 Subject: [PATCH 35/57] refactor: format accept_encoding and content_coding modules --- src/common/accept_encoding.rs | 12 ++++++---- src/common/content_coding.rs | 45 +++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/common/accept_encoding.rs b/src/common/accept_encoding.rs index 245054cc..436a39d4 100644 --- a/src/common/accept_encoding.rs +++ b/src/common/accept_encoding.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; -use {ContentCoding, HeaderValue}; use util::{QualityValue, TryFromValues}; +use {ContentCoding, HeaderValue}; /// `Accept-Encoding` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.4) @@ -78,7 +78,11 @@ impl AcceptEncoding { /// assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::GZIP)); /// ``` pub fn prefered_encoding(&self) -> Option { - self.0.iter().peekable().peek().map(|s| ContentCoding::from_str(*s)) + self.0 + .iter() + .peekable() + .peek() + .map(|s| ContentCoding::from_str(*s)) } /// Returns a quality sorted iterator of the `ContentCoding` @@ -102,9 +106,9 @@ impl AcceptEncoding { } /// Returns a quality sorted iterator of values - /// + /// /// # Example - /// + /// /// ``` /// use headers::{AcceptEncoding, ContentCoding, HeaderValue}; /// diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs index 777b414d..8ce77a39 100644 --- a/src/common/content_coding.rs +++ b/src/common/content_coding.rs @@ -2,11 +2,11 @@ use HeaderValue; // Derives an enum to represent content codings and some helpful impls macro_rules! define_content_coding { - ($($coding:ident; $str:expr,)+) => { + ($($coding:ident; $str:expr,)+) => { #[derive(Copy, Clone, Debug, Eq, PartialEq)] - /// Values that are used with headers like [`Content-Encoding`](self::ContentEncoding) or + /// Values that are used with headers like [`Content-Encoding`](self::ContentEncoding) or /// [`Accept-Encoding`](self::AcceptEncoding) - /// + /// /// [RFC7231](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) pub enum ContentCoding { $( @@ -17,12 +17,12 @@ macro_rules! define_content_coding { impl ContentCoding { /// Returns a `&'static str` for a `ContentCoding` - /// + /// /// # Example - /// + /// /// ``` /// use headers::ContentCoding; - /// + /// /// let coding = ContentCoding::BROTLI; /// assert_eq!(coding.to_static(), "br"); /// ``` @@ -33,20 +33,20 @@ macro_rules! define_content_coding { } } - /// Given a `&str` returns a `ContentCoding` - /// - /// Note this will never fail, in the case of `&str` being an invalid content coding, - /// will return `ContentCoding::IDENTITY` because `'identity'` is generally always an + /// Given a `&str` returns a `ContentCoding` + /// + /// Note this will never fail, in the case of `&str` being an invalid content coding, + /// will return `ContentCoding::IDENTITY` because `'identity'` is generally always an /// accepted coding. - /// + /// /// # Example - /// + /// /// ``` /// use headers::ContentCoding; - /// + /// /// let invalid = ContentCoding::from_str("not a valid coding"); /// assert_eq!(invalid, ContentCoding::IDENTITY); - /// + /// /// let valid = ContentCoding::from_str("gzip"); /// assert_eq!(valid, ContentCoding::GZIP); /// ``` @@ -56,18 +56,18 @@ macro_rules! define_content_coding { } /// Given a `&str` will try to return a `ContentCoding` - /// + /// /// Different from `ContentCoding::from_str(&str)`, if `&str` is an invalid content /// coding, it will return `Err(())` - /// + /// /// # Example - /// + /// /// ``` /// use headers::ContentCoding; - /// + /// /// let invalid = ContentCoding::try_from_str("not a valid coding"); /// assert!(invalid.is_err()); - /// + /// /// let valid = ContentCoding::try_from_str("gzip"); /// assert_eq!(valid.unwrap(), ContentCoding::GZIP); /// ``` @@ -128,7 +128,10 @@ mod tests { fn from_str() { assert_eq!(ContentCoding::from_str("br"), ContentCoding::BROTLI); assert_eq!(ContentCoding::from_str("GZIP"), ContentCoding::GZIP); - assert_eq!(ContentCoding::from_str("blah blah"), ContentCoding::IDENTITY); + assert_eq!( + ContentCoding::from_str("blah blah"), + ContentCoding::IDENTITY + ); } #[test] @@ -136,4 +139,4 @@ mod tests { assert_eq!(ContentCoding::try_from_str("br"), Ok(ContentCoding::BROTLI)); assert_eq!(ContentCoding::try_from_str("blah blah"), Err(())); } -} \ No newline at end of file +} From de3743031f4df7a2936a75ed30531cd039294f83 Mon Sep 17 00:00:00 2001 From: tottoto Date: Wed, 22 Mar 2023 02:55:43 +0900 Subject: [PATCH 36/57] 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 564879fd70d67e0131ce7e5106ce29446ab26a22 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Fri, 14 Apr 2023 01:17:06 +0200 Subject: [PATCH 37/57] v0.3.8 --- Cargo.toml | 8 ++++---- README.md | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a71efe4..93348709 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "headers" +name = "headers-accept-encoding" version = "0.3.8" # don't forget to update html_root_url -description = "typed HTTP headers" +description = "Hypper typed HTTP headers with Accept-Encoding support" license = "MIT" readme = "README.md" homepage = "https://hyper.rs" repository = "https://github.com/hyperium/headers" -authors = ["Sean McArthur "] +authors = ["Sean McArthur ", "Jose Quintana "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] @@ -18,7 +18,7 @@ members = [ [dependencies] http = "0.2.0" -headers-core = { version = "0.2", path = "./headers-core" } +headers-core = "0.2" base64 = "0.13" bitflags = "1.0" itertools = "0.9" diff --git a/README.md b/README.md index a93368fb..65878933 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -# rust http headers +# rust http headers with accept-encoding support [![Build Status](https://github.com/hyperium/headers/workflows/CI/badge.svg)](https://github.com/hyperium/headers/actions?query=workflow%3ACI) Typed HTTP headers. + +**NOTE:** This is a fork but synced with the upstream [hyperium/headers](https://github.com/hyperium/headers) and used by [static-web-server](https://github.com/static-web-server/static-web-server). From 8c9e99e0ac5640507071cb51e4a94a0cb7e8a04d Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Tue, 18 Apr 2023 22:19:14 +0200 Subject: [PATCH 38/57] feat: `zstd` content-coding support --- src/common/content_coding.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/content_coding.rs b/src/common/content_coding.rs index 8ce77a39..aedf9a4f 100644 --- a/src/common/content_coding.rs +++ b/src/common/content_coding.rs @@ -108,6 +108,7 @@ define_content_coding! { DEFLATE; "deflate", GZIP; "gzip", IDENTITY; "identity", + ZSTD; "zstd", } #[cfg(test)] @@ -128,6 +129,7 @@ mod tests { fn from_str() { assert_eq!(ContentCoding::from_str("br"), ContentCoding::BROTLI); assert_eq!(ContentCoding::from_str("GZIP"), ContentCoding::GZIP); + assert_eq!(ContentCoding::from_str("zstd"), ContentCoding::ZSTD); assert_eq!( ContentCoding::from_str("blah blah"), ContentCoding::IDENTITY @@ -137,6 +139,7 @@ mod tests { #[test] fn try_from_str() { assert_eq!(ContentCoding::try_from_str("br"), Ok(ContentCoding::BROTLI)); + assert_eq!(ContentCoding::try_from_str("zstd"), Ok(ContentCoding::ZSTD)); assert_eq!(ContentCoding::try_from_str("blah blah"), Err(())); } } From 7abc45b88fa04b5bb27efa7393c2d818bb360c6f Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Thu, 20 Apr 2023 00:36:10 +0200 Subject: [PATCH 39/57] v1.0.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 93348709..e1adc681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers-accept-encoding" -version = "0.3.8" # don't forget to update html_root_url +version = "1.0.0" # don't forget to update html_root_url description = "Hypper typed HTTP headers with Accept-Encoding support" license = "MIT" readme = "README.md" From f01cc90cf8d601a716856bc9d29f47df92b779e4 Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 21 Apr 2023 22:37:10 +0900 Subject: [PATCH 40/57] 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 c77c1f42bb70b42fac5852ae539dc831065a2b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 30 Aug 2023 14:55:08 +0000 Subject: [PATCH 41/57] Raise msrv to 1.56 (#147) Necessary as httpdate 1.0.3 introduced MSRV, choosing 1.56 --- .github/workflows/ci.yml | 2 +- Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbd9179b..50856fd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] env: - minrust: 1.51.0 + minrust: 1.56.0 jobs: test: diff --git a/Cargo.toml b/Cargo.toml index 4fddd89a..c7246c94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/hyperium/headers" authors = ["Sean McArthur "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] +rust-version = "1.56" [workspace] members = [ From a8b4181730224accd79ecf8074dd9ce0f5cae4be Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 30 Aug 2023 20:29:38 +0530 Subject: [PATCH 42/57] Update base64 to 0.21 (#127) Signed-off-by: Ayush Singh Co-authored-by: Sean McArthur --- 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 c7246c94..b0f31b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ [dependencies] http = "0.2.0" headers-core = { version = "0.2", path = "./headers-core" } -base64 = "0.13" +base64 = "0.21.3" 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"); From 2b9fc5be92f0346482aa6d09917a434a56ade3f3 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 31 Aug 2023 10:37:55 -0400 Subject: [PATCH 43/57] headers:v0.3.9 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0f31b65..65435b57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.8" # don't forget to update html_root_url +version = "0.3.9" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 971d5677..7d1ddaed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.8")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.9")] //! # Typed HTTP Headers //! From 6f1ffec76124f5b8245b699fb924f856e0d4ee15 Mon Sep 17 00:00:00 2001 From: tottoto Date: Wed, 22 Mar 2023 02:55:43 +0900 Subject: [PATCH 44/57] Remove bitflags (#135) --- Cargo.toml | 2 -- src/common/cache_control.rs | 37 ++++++++++++++++++++++++++----------- src/lib.rs | 2 -- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e1adc681..317d696e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,6 @@ members = [ http = "0.2.0" headers-core = "0.2" base64 = "0.13" -bitflags = "1.0" -itertools = "0.9" 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 a8ffe31d..3bd9c688 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 158fc162b5ee0549dd90736e6fd5269800796bbf Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 21 Apr 2023 22:37:10 +0900 Subject: [PATCH 45/57] 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 1dd7f5046787cd021df90aee43ab46102ac24152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 30 Aug 2023 14:55:08 +0000 Subject: [PATCH 46/57] Raise msrv to 1.56 (#147) Necessary as httpdate 1.0.3 introduced MSRV, choosing 1.56 --- .github/workflows/ci.yml | 2 +- Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbd9179b..50856fd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] env: - minrust: 1.51.0 + minrust: 1.56.0 jobs: test: diff --git a/Cargo.toml b/Cargo.toml index 317d696e..ab456a93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/hyperium/headers" authors = ["Sean McArthur ", "Jose Quintana "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] +rust-version = "1.56" [workspace] members = [ From ee329f8bd7585c15ebbf456191a0ebc41022f187 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 30 Aug 2023 20:29:38 +0530 Subject: [PATCH 47/57] Update base64 to 0.21 (#127) Signed-off-by: Ayush Singh Co-authored-by: Sean McArthur --- Cargo.toml | 4 ++-- src/common/authorization.rs | 8 +++++--- src/common/sec_websocket_accept.rs | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab456a93..ca87484a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ members = [ [dependencies] http = "0.2.0" -headers-core = "0.2" -base64 = "0.13" +headers-core = { version = "0.2" } +base64 = "0.21.3" 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"); From ed6d19fd1350cb6cbc6e78abe84e2d95b3699625 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 31 Aug 2023 10:37:55 -0400 Subject: [PATCH 48/57] headers:v0.3.9 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3bd9c688..09b8e203 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.8")] +#![doc(html_root_url = "https://docs.rs/headers/0.3.9")] //! # Typed HTTP Headers //! From af3b26b94ff37e0221f2811d953d315073011d31 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sat, 16 Sep 2023 15:22:05 +0200 Subject: [PATCH 49/57] v1.0.1 --- Cargo.toml | 7 ++++--- README.md | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca87484a..1761da5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "headers-accept-encoding" -version = "1.0.0" # don't forget to update html_root_url -description = "Hypper typed HTTP headers with Accept-Encoding support" +version = "1.0.1" # don't forget to update html_root_url +description = "Hypper typed HTTP headers with Accept-Encoding + zstd support" license = "MIT" readme = "README.md" homepage = "https://hyper.rs" -repository = "https://github.com/hyperium/headers" +repository = "https://github.com/static-web-server/headers-accept-encoding" authors = ["Sean McArthur ", "Jose Quintana "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] @@ -25,6 +25,7 @@ bytes = "1" mime = "0.3.14" sha1 = "0.10" httpdate = "1" +itertools = "0.11" [features] nightly = [] diff --git a/README.md b/README.md index 65878933..c81c6dae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# rust http headers with accept-encoding support +# Rust HTTP headers with Accept-Encoding + zstd Content Coding support [![Build Status](https://github.com/hyperium/headers/workflows/CI/badge.svg)](https://github.com/hyperium/headers/actions?query=workflow%3ACI) -Typed HTTP headers. +A [headers crate fork](https://github.com/ParkMyCar/headers) with `Accept-Encoding` + zstd Content Coding support and synced with the upstream [hyperium/headers](https://github.com/hyperium/headers). -**NOTE:** This is a fork but synced with the upstream [hyperium/headers](https://github.com/hyperium/headers) and used by [static-web-server](https://github.com/static-web-server/static-web-server). +This fork is used by [static-web-server](https://github.com/static-web-server/static-web-server). From a3f982792dc5200bc8c8ec64879102b2dcc02159 Mon Sep 17 00:00:00 2001 From: bc-universe <9028220+bc-universe@users.noreply.github.com> Date: Fri, 3 Nov 2023 20:37:58 +0100 Subject: [PATCH 50/57] Add support for "must-understand" directive (#149) * Add support for "must-understand" directive * Address fmt issues --- src/common/cache_control.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/common/cache_control.rs b/src/common/cache_control.rs index afb69249..7d5db055 100644 --- a/src/common/cache_control.rs +++ b/src/common/cache_control.rs @@ -59,6 +59,7 @@ impl Flags { const PRIVATE: Self = Self { bits: 0b001000000 }; const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 }; const IMMUTABLE: Self = Self { bits: 0b100000000 }; + const MUST_UNDERSTAND: Self = Self { bits: 0b1000000000 }; fn empty() -> Self { Self { bits: 0 } @@ -121,6 +122,10 @@ impl CacheControl { pub fn immutable(&self) -> bool { self.flags.contains(Flags::IMMUTABLE) } + /// Check if the `must_understand` directive is set. + pub fn must_understand(&self) -> bool { + self.flags.contains(Flags::MUST_UNDERSTAND) + } /// Get the value of the `max-age` directive if set. pub fn max_age(&self) -> Option { @@ -186,6 +191,11 @@ impl CacheControl { self } + /// Set the `must_understand` directive. + pub fn with_must_understand(mut self) -> Self { + self.flags.insert(Flags::MUST_UNDERSTAND); + self + } /// Set the `max-age` directive. pub fn with_max_age(mut self, duration: Duration) -> Self { self.max_age = Some(duration.into()); @@ -258,6 +268,9 @@ impl FromIterator for FromIter { Directive::MustRevalidate => { cc.flags.insert(Flags::MUST_REVALIDATE); } + Directive::MustUnderstand => { + cc.flags.insert(Flags::MUST_UNDERSTAND); + } Directive::Public => { cc.flags.insert(Flags::PUBLIC); } @@ -310,6 +323,7 @@ impl<'a> fmt::Display for Fmt<'a> { if_flag(Flags::PUBLIC, Directive::Public), if_flag(Flags::PRIVATE, Directive::Private), if_flag(Flags::IMMUTABLE, Directive::Immutable), + if_flag(Flags::MUST_UNDERSTAND, Directive::MustUnderstand), if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate), self.0 .max_age @@ -355,6 +369,7 @@ enum Directive { // response directives MustRevalidate, + MustUnderstand, Public, Private, Immutable, @@ -376,6 +391,7 @@ impl fmt::Display for Directive { Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs), Directive::MustRevalidate => "must-revalidate", + Directive::MustUnderstand => "must-understand", Directive::Public => "public", Directive::Private => "private", Directive::Immutable => "immutable", @@ -399,6 +415,7 @@ impl FromStr for KnownDirective { "public" => Directive::Public, "private" => Directive::Private, "immutable" => Directive::Immutable, + "must-understand" => Directive::MustUnderstand, "proxy-revalidate" => Directive::ProxyRevalidate, "" => return Err(()), _ => match s.find('=') { @@ -472,6 +489,18 @@ mod tests { assert!(cc.immutable()); } + #[test] + fn test_must_understand() { + let cc = CacheControl::new().with_must_understand(); + let headers = test_encode(cc.clone()); + assert_eq!(headers["cache-control"], "must-understand"); + assert_eq!( + test_decode::(&["must-understand"]).unwrap(), + cc + ); + assert!(cc.must_understand()); + } + #[test] fn test_parse_bad_syntax() { assert_eq!(test_decode::(&["max-age=lolz"]), None); From 4400aa90c47a78a41be5f394bc19f420e2c0244a Mon Sep 17 00:00:00 2001 From: Chrislearn Young Date: Thu, 16 Nov 2023 09:30:53 +0800 Subject: [PATCH 51/57] Update to http 1.0 (#151) --- Cargo.toml | 2 +- headers-core/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65435b57..5037d069 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [dependencies] -http = "0.2.0" +http = "1.0.0" headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.21.3" bytes = "1" diff --git a/headers-core/Cargo.toml b/headers-core/Cargo.toml index 22c011f1..23f2556d 100644 --- a/headers-core/Cargo.toml +++ b/headers-core/Cargo.toml @@ -10,4 +10,4 @@ authors = ["Sean McArthur "] keywords = ["http", "headers", "hyper", "hyperium"] [dependencies] -http = "0.2.0" +http = "1.0.0" From 7d784cdf78f0a9fed82f2902041b17f66be3ef76 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 20 Nov 2023 06:14:28 -0800 Subject: [PATCH 52/57] Make authorization schemes case and whitespace insensitive (#153) * Make authorization schemes case and whitespace insensitive According to RFC7235[1]: > It uses a case- > insensitive token as a means to identify the authentication scheme, > followed by additional information necessary for achieving > authentication via that scheme. [1]: https://datatracker.ietf.org/doc/html/rfc7235#section-2.1 * Use eq_ignore_ascii_case() for Authorization schemes This is effectively the same as comparing the result of `to_ascii_lowercase()`, with the benefit of avoiding "allocating and copying temporaries" (according to the Rust `std` docs[1]). [1]: https://doc.rust-lang.org/std/primitive.slice.html#method.eq_ignore_ascii_case --------- Co-authored-by: Simon Bihel --- src/common/authorization.rs | 38 ++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/common/authorization.rs b/src/common/authorization.rs index e62f2fd9..bb2c35ec 100644 --- a/src/common/authorization.rs +++ b/src/common/authorization.rs @@ -82,9 +82,9 @@ impl ::Header for Authorization { .next() .and_then(|val| { let slice = val.as_bytes(); - if slice.starts_with(C::SCHEME.as_bytes()) - && slice.len() > C::SCHEME.len() + if slice.len() > C::SCHEME.len() && slice[C::SCHEME.len()] == b' ' + && slice[..C::SCHEME.len()].eq_ignore_ascii_case(C::SCHEME.as_bytes()) { C::decode(val).map(Authorization) } else { @@ -151,7 +151,7 @@ impl Credentials for Basic { fn decode(value: &HeaderValue) -> Option { debug_assert!( - value.as_bytes().starts_with(b"Basic "), + value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()), "HeaderValue to decode should start with \"Basic ..\", received = {:?}", value, ); @@ -186,7 +186,7 @@ pub struct Bearer(HeaderValueString); impl Bearer { /// View the token part as a `&str`. pub fn token(&self) -> &str { - &self.0.as_str()["Bearer ".len()..] + self.0.as_str()["Bearer ".len()..].trim_start() } } @@ -195,7 +195,7 @@ impl Credentials for Bearer { fn decode(value: &HeaderValue) -> Option { debug_assert!( - value.as_bytes().starts_with(b"Bearer "), + value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()), "HeaderValue to decode should start with \"Bearer ..\", received = {:?}", value, ); @@ -252,6 +252,22 @@ mod tests { assert_eq!(auth.0.password(), "open sesame"); } + #[test] + fn basic_decode_case_insensitive() { + let auth: Authorization = + test_decode(&["basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), "open sesame"); + } + + #[test] + fn basic_decode_extra_whitespaces() { + let auth: Authorization = + test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), "open sesame"); + } + #[test] fn basic_decode_no_password() { let auth: Authorization = test_decode(&["Basic QWxhZGRpbjo="]).unwrap(); @@ -273,6 +289,18 @@ mod tests { let auth: Authorization = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); } + + #[test] + fn bearer_decode_case_insensitive() { + let auth: Authorization = test_decode(&["bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); + assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); + } + + #[test] + fn bearer_decode_extra_whitespaces() { + let auth: Authorization = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); + assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); + } } //bench_header!(raw, Authorization, { vec![b"foo bar baz".to_vec()] }); From f2c4aba02044249cc6a3e224c9ff930fd0999ad3 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 24 Nov 2023 09:00:51 -0500 Subject: [PATCH 53/57] fix: Range suffixes are not Rust RangeTo (#155) An HTTP Range of `bytes=-100` means a suffix, the last 100 bytes. This was wrongly parsed as the Rust range `..100`, which means the first 100 bytes. This has been fixed, but doing so required change `Range::iter` to accept a length argument, to determine if the ranges are satisfiable. BREAKING CHANGE: Change `.iter()` calls to `.satisfiable_ranges(len)`. Also, the `Range::bytes()` constructor will now return an error if pass a `RangeTo` (e.g. `Range::bytes(..100)`). --- src/common/range.rs | 49 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/common/range.rs b/src/common/range.rs index 29cc79d3..6d35936d 100644 --- a/src/common/range.rs +++ b/src/common/range.rs @@ -48,29 +48,52 @@ impl Range { /// Creates a `Range` header from bounds. pub fn bytes(bounds: impl RangeBounds) -> Result { let v = match (bounds.start_bound(), bounds.end_bound()) { - (Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end), - (Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1), (Bound::Included(start), Bound::Included(end)) => format!("bytes={}-{}", start, end), (Bound::Included(start), Bound::Excluded(&end)) => { format!("bytes={}-{}", start, end - 1) } (Bound::Included(start), Bound::Unbounded) => format!("bytes={}-", start), + // These do not directly translate. + //(Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end), + //(Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1), _ => return Err(InvalidRange { _inner: () }), }; Ok(Range(::HeaderValue::from_str(&v).unwrap())) } - /// Iterate the range sets as a tuple of bounds. - pub fn iter<'a>(&'a self) -> impl Iterator, Bound)> + 'a { + /// Iterate the range sets as a tuple of bounds, if valid with length. + /// + /// The length of the content is passed as an argument, and all ranges + /// that can be satisfied will be iterated. + pub fn satisfiable_ranges<'a>( + &'a self, + len: u64, + ) -> impl Iterator, Bound)> + 'a { let s = self .0 .to_str() .expect("valid string checked in Header::decode()"); - s["bytes=".len()..].split(',').filter_map(|spec| { + s["bytes=".len()..].split(',').filter_map(move |spec| { let mut iter = spec.trim().splitn(2, '-'); - Some((parse_bound(iter.next()?)?, parse_bound(iter.next()?)?)) + let start = parse_bound(iter.next()?)?; + let end = parse_bound(iter.next()?)?; + + // Unbounded ranges in HTTP are actually a suffix + // For example, `-100` means the last 100 bytes. + if let Bound::Unbounded = start { + if let Bound::Included(end) = end { + if len < end { + // Last N bytes is larger than available! + return None; + } + return Some((Bound::Included(len - end), Bound::Unbounded)); + } + // else fall through + } + + Some((start, end)) }) } } @@ -416,3 +439,17 @@ fn test_byte_range_spec_to_satisfiable_range() { bench_header!(bytes_multi, Range, { vec![b"bytes=1-1001,2001-3001,10001-".to_vec()]}); bench_header!(custom_unit, Range, { vec![b"other=0-100000".to_vec()]}); */ + +#[test] +fn test_to_satisfiable_range_suffix() { + let range = super::test_decode::(&["bytes=-100"]).unwrap(); + let bounds = range.satisfiable_ranges(350).next().unwrap(); + assert_eq!(bounds, (Bound::Included(250), Bound::Unbounded)); +} + +#[test] +fn test_to_unsatisfiable_range_suffix() { + let range = super::test_decode::(&["bytes=-350"]).unwrap(); + let bounds = range.satisfiable_ranges(100).next(); + assert_eq!(bounds, None); +} From 6ebb42d8e1a1db0b637ea8b4074eeb087af935b8 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 24 Nov 2023 12:07:54 -0500 Subject: [PATCH 54/57] update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index aa33b8e7..985a757d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2019 Sean McArthur +Copyright (c) 2014-2023 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From b8cf384cc40c75a659240ea7d07898e65db72d4e Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 24 Nov 2023 14:12:48 -0500 Subject: [PATCH 55/57] headers-core:v0.3.0 --- Cargo.toml | 2 +- headers-core/Cargo.toml | 2 +- headers-core/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5037d069..1c14c164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ [dependencies] http = "1.0.0" -headers-core = { version = "0.2", path = "./headers-core" } +headers-core = { version = "0.3", path = "./headers-core" } base64 = "0.21.3" bytes = "1" mime = "0.3.14" diff --git a/headers-core/Cargo.toml b/headers-core/Cargo.toml index 23f2556d..60ecec29 100644 --- a/headers-core/Cargo.toml +++ b/headers-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers-core" -version = "0.2.0" # don't forget to update html_root_url +version = "0.3.0" # don't forget to update html_root_url description = "typed HTTP headers core trait" license = "MIT" readme = "README.md" diff --git a/headers-core/src/lib.rs b/headers-core/src/lib.rs index 92e3d15e..5692b65a 100644 --- a/headers-core/src/lib.rs +++ b/headers-core/src/lib.rs @@ -1,7 +1,7 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] -#![doc(html_root_url = "https://docs.rs/headers-core/0.2.0")] +#![doc(html_root_url = "https://docs.rs/headers-core/0.3.0")] //! # headers-core //! From ad331d0bd57700e4daadf5c370ff9e2295dd8b8a Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 24 Nov 2023 14:15:00 -0500 Subject: [PATCH 56/57] headers:v0.4.0 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c14c164..05dc58e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers" -version = "0.3.9" # don't forget to update html_root_url +version = "0.4.0" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 7d1ddaed..18c57fba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] -#![doc(html_root_url = "https://docs.rs/headers/0.3.9")] +#![doc(html_root_url = "https://docs.rs/headers/0.4.0")] //! # Typed HTTP Headers //! From b01d6ae1f41374228ad29176ceac90a830fa7912 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Tue, 5 Dec 2023 01:59:12 +0100 Subject: [PATCH 57/57] v1.1.0 --- Cargo.toml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d57eea8..95ae3e9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "headers-accept-encoding" -version = "1.0.1" # don't forget to update html_root_url +version = "1.1.0" # don't forget to update html_root_url description = "Hypper typed HTTP headers with Accept-Encoding + zstd support" license = "MIT" readme = "README.md" @@ -9,14 +9,8 @@ repository = "https://github.com/static-web-server/headers-accept-encoding" authors = ["Sean McArthur ", "Jose Quintana "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] -rust-version = "1.56" -rust-version = "1.56" -[workspace] -members = [ - "./", - "headers-core", -] +rust-version = "1.56" [dependencies] http = "1.0.0"