summaryrefslogtreecommitdiffstats
path: root/third_party/rust/headers/src/util
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/headers/src/util
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/headers/src/util')
-rw-r--r--third_party/rust/headers/src/util/csv.rs38
-rw-r--r--third_party/rust/headers/src/util/entity.rs357
-rw-r--r--third_party/rust/headers/src/util/flat_csv.rs199
-rw-r--r--third_party/rust/headers/src/util/fmt.rs11
-rw-r--r--third_party/rust/headers/src/util/http_date.rs152
-rw-r--r--third_party/rust/headers/src/util/iter.rs11
-rw-r--r--third_party/rust/headers/src/util/mod.rs87
-rw-r--r--third_party/rust/headers/src/util/seconds.rs67
-rw-r--r--third_party/rust/headers/src/util/value_string.rs93
9 files changed, 1015 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/util/csv.rs b/third_party/rust/headers/src/util/csv.rs
new file mode 100644
index 0000000000..2d0a8ed68d
--- /dev/null
+++ b/third_party/rust/headers/src/util/csv.rs
@@ -0,0 +1,38 @@
+use std::fmt;
+
+/// Reads a comma-delimited raw header into a Vec.
+pub(crate) fn from_comma_delimited<'i, I, T, E>(values: &mut I) -> Result<E, ::Error>
+where
+ I: Iterator<Item = &'i ::HeaderValue>,
+ T: ::std::str::FromStr,
+ E: ::std::iter::FromIterator<T>,
+{
+ values
+ .flat_map(|value| {
+ value.to_str().into_iter().flat_map(|string| {
+ string
+ .split(',')
+ .filter_map(|x| match x.trim() {
+ "" => None,
+ y => Some(y),
+ })
+ .map(|x| x.parse().map_err(|_| ::Error::invalid()))
+ })
+ })
+ .collect()
+}
+
+/// Format an array into a comma-delimited string.
+pub(crate) fn fmt_comma_delimited<T: fmt::Display>(
+ f: &mut fmt::Formatter,
+ mut iter: impl Iterator<Item = T>,
+) -> fmt::Result {
+ if let Some(part) = iter.next() {
+ fmt::Display::fmt(&part, f)?;
+ }
+ for part in iter {
+ f.write_str(", ")?;
+ fmt::Display::fmt(&part, f)?;
+ }
+ Ok(())
+}
diff --git a/third_party/rust/headers/src/util/entity.rs b/third_party/rust/headers/src/util/entity.rs
new file mode 100644
index 0000000000..c966b6fe96
--- /dev/null
+++ b/third_party/rust/headers/src/util/entity.rs
@@ -0,0 +1,357 @@
+use std::fmt;
+
+use super::{FlatCsv, IterExt};
+use HeaderValue;
+
+/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
+///
+/// An entity tag consists of a string enclosed by two literal double quotes.
+/// Preceding the first double quote is an optional weakness indicator,
+/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`.
+///
+/// # ABNF
+///
+/// ```text
+/// entity-tag = [ weak ] opaque-tag
+/// weak = %x57.2F ; "W/", case-sensitive
+/// opaque-tag = DQUOTE *etagc DQUOTE
+/// etagc = %x21 / %x23-7E / obs-text
+/// ; VCHAR except double quotes, plus obs-text
+/// ```
+///
+/// # Comparison
+/// To check if two entity tags are equivalent in an application always use the `strong_eq` or
+/// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are
+/// identical.
+///
+/// The example below shows the results for a set of entity-tag pairs and
+/// both the weak and strong comparison function results:
+///
+/// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
+/// |---------|---------|-------------------|-----------------|
+/// | `W/"1"` | `W/"1"` | no match | match |
+/// | `W/"1"` | `W/"2"` | no match | no match |
+/// | `W/"1"` | `"1"` | no match | match |
+/// | `"1"` | `"1"` | match | match |
+#[derive(Clone, Eq, PartialEq)]
+pub(crate) struct EntityTag<T = HeaderValue>(T);
+
+#[derive(Clone, Debug, PartialEq)]
+pub(crate) enum EntityTagRange {
+ Any,
+ Tags(FlatCsv),
+}
+
+// ===== impl EntityTag =====
+
+impl<T: AsRef<[u8]>> EntityTag<T> {
+ /// Get the tag.
+ pub(crate) fn tag(&self) -> &[u8] {
+ let bytes = self.0.as_ref();
+ let end = bytes.len() - 1;
+ if bytes[0] == b'W' {
+ // W/"<tag>"
+ &bytes[3..end]
+ } else {
+ // "<tag>"
+ &bytes[1..end]
+ }
+ }
+
+ /// Return if this is a "weak" tag.
+ pub(crate) fn is_weak(&self) -> bool {
+ self.0.as_ref()[0] == b'W'
+ }
+
+ /// For strong comparison two entity-tags are equivalent if both are not weak and their
+ /// opaque-tags match character-by-character.
+ pub(crate) fn strong_eq<R>(&self, other: &EntityTag<R>) -> bool
+ where
+ R: AsRef<[u8]>,
+ {
+ !self.is_weak() && !other.is_weak() && self.tag() == other.tag()
+ }
+
+ /// For weak comparison two entity-tags are equivalent if their
+ /// opaque-tags match character-by-character, regardless of either or
+ /// both being tagged as "weak".
+ pub(crate) fn weak_eq<R>(&self, other: &EntityTag<R>) -> bool
+ where
+ R: AsRef<[u8]>,
+ {
+ self.tag() == other.tag()
+ }
+
+ /// The inverse of `EntityTag.strong_eq()`.
+ #[cfg(test)]
+ pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool {
+ !self.strong_eq(other)
+ }
+
+ /// The inverse of `EntityTag.weak_eq()`.
+ #[cfg(test)]
+ pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool {
+ !self.weak_eq(other)
+ }
+
+ pub(crate) fn parse(src: T) -> Option<Self> {
+ let slice = src.as_ref();
+ let length = slice.len();
+
+ // Early exits if it doesn't terminate in a DQUOTE.
+ if length < 2 || slice[length - 1] != b'"' {
+ return None;
+ }
+
+ let start = match slice[0] {
+ // "<tag>"
+ b'"' => 1,
+ // W/"<tag>"
+ b'W' => {
+ if length >= 4 && slice[1] == b'/' && slice[2] == b'"' {
+ 3
+ } else {
+ return None;
+ }
+ }
+ _ => return None,
+ };
+
+ if check_slice_validity(&slice[start..length - 1]) {
+ Some(EntityTag(src))
+ } else {
+ None
+ }
+ }
+}
+
+impl EntityTag {
+ /*
+ /// Constructs a new EntityTag.
+ /// # Panics
+ /// If the tag contains invalid characters.
+ pub fn new(weak: bool, tag: String) -> EntityTag {
+ assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
+ EntityTag { weak: weak, tag: tag }
+ }
+
+ /// Constructs a new weak EntityTag.
+ /// # Panics
+ /// If the tag contains invalid characters.
+ pub fn weak(tag: String) -> EntityTag {
+ EntityTag::new(true, tag)
+ }
+
+ /// Constructs a new strong EntityTag.
+ /// # Panics
+ /// If the tag contains invalid characters.
+ pub fn strong(tag: String) -> EntityTag {
+ EntityTag::new(false, tag)
+ }
+ */
+
+ #[cfg(test)]
+ pub fn from_static(bytes: &'static str) -> EntityTag {
+ let val = HeaderValue::from_static(bytes);
+ match EntityTag::from_val(&val) {
+ Some(tag) => tag,
+ None => {
+ panic!("invalid static string for EntityTag: {:?}", bytes);
+ }
+ }
+ }
+
+ pub(crate) fn from_owned(val: HeaderValue) -> Option<EntityTag> {
+ EntityTag::parse(val.as_bytes())?;
+ Some(EntityTag(val))
+ }
+
+ pub(crate) fn from_val(val: &HeaderValue) -> Option<EntityTag> {
+ EntityTag::parse(val.as_bytes()).map(|_entity| {
+ EntityTag(val.clone())
+ })
+ }
+}
+
+impl<T: fmt::Debug> fmt::Debug for EntityTag<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl super::TryFromValues for EntityTag {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ values
+ .just_one()
+ .and_then(EntityTag::from_val)
+ .ok_or_else(::Error::invalid)
+ }
+}
+
+impl From<EntityTag> for HeaderValue {
+ fn from(tag: EntityTag) -> HeaderValue {
+ tag.0
+ }
+}
+
+impl<'a> From<&'a EntityTag> for HeaderValue {
+ fn from(tag: &'a EntityTag) -> HeaderValue {
+ tag.0.clone()
+ }
+}
+
+/// check that each char in the slice is either:
+/// 1. `%x21`, or
+/// 2. in the range `%x23` to `%x7E`, or
+/// 3. above `%x80`
+fn check_slice_validity(slice: &[u8]) -> bool {
+ slice.iter().all(|&c| {
+ // HeaderValue already validates that this doesnt contain control
+ // characters, so we only need to look for DQUOTE (`"`).
+ //
+ // The debug_assert is just in case we use check_slice_validity in
+ // some new context that didnt come from a HeaderValue.
+ debug_assert!(
+ (c >= b'\x21' && c <= b'\x7e') | (c >= b'\x80'),
+ "EntityTag expects HeaderValue to have check for control characters"
+ );
+ c != b'"'
+ })
+}
+
+// ===== impl EntityTagRange =====
+
+impl EntityTagRange {
+ pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool {
+ self.matches_if(entity, |a, b| a.strong_eq(b))
+ }
+
+ pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool {
+ self.matches_if(entity, |a, b| a.weak_eq(b))
+ }
+
+ fn matches_if<F>(&self, entity: &EntityTag, func: F) -> bool
+ where
+ F: Fn(&EntityTag<&str>, &EntityTag) -> bool,
+ {
+ match *self {
+ EntityTagRange::Any => true,
+ EntityTagRange::Tags(ref tags) => {
+ tags.iter()
+ .flat_map(EntityTag::<&str>::parse)
+ .any(|tag| func(&tag, entity))
+ },
+ }
+ }
+}
+
+impl super::TryFromValues for EntityTagRange {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ let flat = FlatCsv::try_from_values(values)?;
+ if flat.value == "*" {
+ Ok(EntityTagRange::Any)
+ } else {
+ Ok(EntityTagRange::Tags(flat))
+ }
+ }
+}
+
+impl<'a> From<&'a EntityTagRange> for HeaderValue {
+ fn from(tag: &'a EntityTagRange) -> HeaderValue {
+ match *tag {
+ EntityTagRange::Any => HeaderValue::from_static("*"),
+ EntityTagRange::Tags(ref tags) => tags.into(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn parse(slice: &[u8]) -> Option<EntityTag> {
+ let val = HeaderValue::from_bytes(slice).ok()?;
+ EntityTag::from_val(&val)
+ }
+
+ #[test]
+ fn test_etag_parse_success() {
+ // Expected success
+ let tag = parse(b"\"foobar\"").unwrap();
+ assert!(!tag.is_weak());
+ assert_eq!(tag.tag(), b"foobar");
+
+ let weak = parse(b"W/\"weaktag\"").unwrap();
+ assert!(weak.is_weak());
+ assert_eq!(weak.tag(), b"weaktag");
+ }
+
+ #[test]
+ fn test_etag_parse_failures() {
+ // Expected failures
+ macro_rules! fails {
+ ($slice:expr) => {
+ assert_eq!(parse($slice), None);
+ };
+ }
+
+ fails!(b"no-dquote");
+ fails!(b"w/\"the-first-w-is-case sensitive\"");
+ fails!(b"W/\"");
+ fails!(b"");
+ fails!(b"\"unmatched-dquotes1");
+ fails!(b"unmatched-dquotes2\"");
+ fails!(b"\"inner\"quotes\"");
+ }
+
+ /*
+ #[test]
+ fn test_etag_fmt() {
+ assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\"");
+ assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
+ assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"");
+ assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\"");
+ assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
+ }
+ */
+
+ #[test]
+ fn test_cmp() {
+ // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
+ // |---------|---------|-------------------|-----------------|
+ // | `W/"1"` | `W/"1"` | no match | match |
+ // | `W/"1"` | `W/"2"` | no match | no match |
+ // | `W/"1"` | `"1"` | no match | match |
+ // | `"1"` | `"1"` | match | match |
+ let mut etag1 = EntityTag::from_static("W/\"1\"");
+ let mut etag2 = etag1.clone();
+ assert!(!etag1.strong_eq(&etag2));
+ assert!(etag1.weak_eq(&etag2));
+ assert!(etag1.strong_ne(&etag2));
+ assert!(!etag1.weak_ne(&etag2));
+
+ etag2 = EntityTag::from_static("W/\"2\"");
+ assert!(!etag1.strong_eq(&etag2));
+ assert!(!etag1.weak_eq(&etag2));
+ assert!(etag1.strong_ne(&etag2));
+ assert!(etag1.weak_ne(&etag2));
+
+ etag2 = EntityTag::from_static("\"1\"");
+ assert!(!etag1.strong_eq(&etag2));
+ assert!(etag1.weak_eq(&etag2));
+ assert!(etag1.strong_ne(&etag2));
+ assert!(!etag1.weak_ne(&etag2));
+
+ etag1 = EntityTag::from_static("\"1\"");
+ assert!(etag1.strong_eq(&etag2));
+ assert!(etag1.weak_eq(&etag2));
+ assert!(!etag1.strong_ne(&etag2));
+ assert!(!etag1.weak_ne(&etag2));
+ }
+}
diff --git a/third_party/rust/headers/src/util/flat_csv.rs b/third_party/rust/headers/src/util/flat_csv.rs
new file mode 100644
index 0000000000..099b034212
--- /dev/null
+++ b/third_party/rust/headers/src/util/flat_csv.rs
@@ -0,0 +1,199 @@
+use std::fmt;
+use std::iter::FromIterator;
+use std::marker::PhantomData;
+
+use bytes::BytesMut;
+use util::TryFromValues;
+use HeaderValue;
+
+// A single `HeaderValue` that can flatten multiple values with commas.
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub(crate) struct FlatCsv<Sep = Comma> {
+ pub(crate) value: HeaderValue,
+ _marker: PhantomData<Sep>,
+}
+
+pub(crate) trait Separator {
+ const BYTE: u8;
+ const CHAR: char;
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) enum Comma {}
+
+impl Separator for Comma {
+ const BYTE: u8 = b',';
+ const CHAR: char = ',';
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) enum SemiColon {}
+
+impl Separator for SemiColon {
+ const BYTE: u8 = b';';
+ const CHAR: char = ';';
+}
+
+impl<Sep: Separator> FlatCsv<Sep> {
+ pub(crate) fn iter(&self) -> impl Iterator<Item = &str> {
+ self.value.to_str().ok().into_iter().flat_map(|value_str| {
+ let mut in_quotes = false;
+ value_str
+ .split(move |c| {
+ if in_quotes {
+ if c == '"' {
+ in_quotes = false;
+ }
+ false // dont split
+ } else {
+ if c == Sep::CHAR {
+ true // split
+ } else {
+ if c == '"' {
+ in_quotes = true;
+ }
+ false // dont split
+ }
+ }
+ })
+ .map(|item| item.trim())
+ })
+ }
+}
+
+impl<Sep: Separator> TryFromValues for FlatCsv<Sep> {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ let flat = values.collect();
+ Ok(flat)
+ }
+}
+
+impl<Sep> From<HeaderValue> for FlatCsv<Sep> {
+ fn from(value: HeaderValue) -> Self {
+ FlatCsv {
+ value,
+ _marker: PhantomData,
+ }
+ }
+}
+
+impl<'a, Sep> From<&'a FlatCsv<Sep>> for HeaderValue {
+ fn from(flat: &'a FlatCsv<Sep>) -> HeaderValue {
+ flat.value.clone()
+ }
+}
+
+impl<Sep> fmt::Debug for FlatCsv<Sep> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.value, f)
+ }
+}
+
+impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv<Sep> {
+ fn from_iter<I>(iter: I) -> Self
+ where
+ I: IntoIterator<Item = &'a HeaderValue>,
+ {
+ let mut values = iter.into_iter();
+
+ // Common case is there is only 1 value, optimize for that
+ if let (1, Some(1)) = values.size_hint() {
+ return values
+ .next()
+ .expect("size_hint claimed 1 item")
+ .clone()
+ .into();
+ }
+
+ // Otherwise, there are multiple, so this should merge them into 1.
+ let mut buf = values
+ .next()
+ .cloned()
+ .map(|val| BytesMut::from(val.as_bytes()))
+ .unwrap_or_else(|| BytesMut::new());
+
+ for val in values {
+ buf.extend_from_slice(&[Sep::BYTE, b' ']);
+ buf.extend_from_slice(val.as_bytes());
+ }
+
+ let val =
+ HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid");
+
+ val.into()
+ }
+}
+
+// TODO: would be great if there was a way to de-dupe these with above
+impl<Sep: Separator> FromIterator<HeaderValue> for FlatCsv<Sep> {
+ fn from_iter<I>(iter: I) -> Self
+ where
+ I: IntoIterator<Item = HeaderValue>,
+ {
+ let mut values = iter.into_iter();
+
+ // Common case is there is only 1 value, optimize for that
+ if let (1, Some(1)) = values.size_hint() {
+ return values.next().expect("size_hint claimed 1 item").into();
+ }
+
+ // Otherwise, there are multiple, so this should merge them into 1.
+ let mut buf = values
+ .next()
+ .map(|val| BytesMut::from(val.as_bytes()))
+ .unwrap_or_else(|| BytesMut::new());
+
+ for val in values {
+ buf.extend_from_slice(&[Sep::BYTE, b' ']);
+ buf.extend_from_slice(val.as_bytes());
+ }
+
+ let val =
+ HeaderValue::from_maybe_shared(buf.freeze()).expect("comma separated HeaderValues are valid");
+
+ val.into()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn comma() {
+ let val = HeaderValue::from_static("aaa, b; bb, ccc");
+ let csv = FlatCsv::<Comma>::from(val);
+
+ let mut values = csv.iter();
+ assert_eq!(values.next(), Some("aaa"));
+ assert_eq!(values.next(), Some("b; bb"));
+ assert_eq!(values.next(), Some("ccc"));
+ assert_eq!(values.next(), None);
+ }
+
+ #[test]
+ fn semicolon() {
+ let val = HeaderValue::from_static("aaa; b, bb; ccc");
+ let csv = FlatCsv::<SemiColon>::from(val);
+
+ let mut values = csv.iter();
+ assert_eq!(values.next(), Some("aaa"));
+ assert_eq!(values.next(), Some("b, bb"));
+ assert_eq!(values.next(), Some("ccc"));
+ assert_eq!(values.next(), None);
+ }
+
+ #[test]
+ fn quoted_text() {
+ let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes");
+ let csv = FlatCsv::<Comma>::from(val);
+
+ let mut values = csv.iter();
+ assert_eq!(values.next(), Some("foo=\"bar,baz\""));
+ assert_eq!(values.next(), Some("sherlock=holmes"));
+ assert_eq!(values.next(), None);
+ }
+}
diff --git a/third_party/rust/headers/src/util/fmt.rs b/third_party/rust/headers/src/util/fmt.rs
new file mode 100644
index 0000000000..c535c72925
--- /dev/null
+++ b/third_party/rust/headers/src/util/fmt.rs
@@ -0,0 +1,11 @@
+use std::fmt::Display;
+
+use HeaderValue;
+
+pub(crate) fn fmt<T: Display>(fmt: T) -> HeaderValue {
+ let s = fmt.to_string();
+ match HeaderValue::from_maybe_shared(s) {
+ Ok(val) => val,
+ Err(err) => panic!("illegal HeaderValue; error = {:?}, fmt = \"{}\"", err, fmt),
+ }
+}
diff --git a/third_party/rust/headers/src/util/http_date.rs b/third_party/rust/headers/src/util/http_date.rs
new file mode 100644
index 0000000000..da3f83969d
--- /dev/null
+++ b/third_party/rust/headers/src/util/http_date.rs
@@ -0,0 +1,152 @@
+use std::fmt;
+use std::str::FromStr;
+use std::time::SystemTime;
+
+use bytes::Bytes;
+use http::header::HeaderValue;
+use httpdate;
+
+use super::IterExt;
+
+/// A timestamp with HTTP formatting and parsing
+// Prior to 1995, there were three different formats commonly used by
+// servers to communicate timestamps. For compatibility with old
+// implementations, all three are defined here. The preferred format is
+// a fixed-length and single-zone subset of the date and time
+// specification used by the Internet Message Format [RFC5322].
+//
+// HTTP-date = IMF-fixdate / obs-date
+//
+// An example of the preferred format is
+//
+// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
+//
+// Examples of the two obsolete formats are
+//
+// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
+// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+//
+// A recipient that parses a timestamp value in an HTTP header field
+// MUST accept all three HTTP-date formats. When a sender generates a
+// header field that contains one or more timestamps defined as
+// 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(httpdate::HttpDate);
+
+impl HttpDate {
+ pub(crate) fn from_val(val: &HeaderValue) -> Option<Self> {
+ val.to_str().ok()?.parse().ok()
+ }
+}
+
+// TODO: remove this and FromStr?
+#[derive(Debug)]
+pub struct Error(());
+
+impl super::TryFromValues for HttpDate {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ values
+ .just_one()
+ .and_then(HttpDate::from_val)
+ .ok_or_else(::Error::invalid)
+ }
+}
+
+impl From<HttpDate> for HeaderValue {
+ fn from(date: HttpDate) -> HeaderValue {
+ (&date).into()
+ }
+}
+
+impl<'a> From<&'a HttpDate> for HeaderValue {
+ fn from(date: &'a HttpDate) -> HeaderValue {
+ // TODO: could be just BytesMut instead of String
+ let s = date.to_string();
+ let bytes = Bytes::from(s);
+ HeaderValue::from_maybe_shared(bytes).expect("HttpDate always is a valid value")
+ }
+}
+
+impl FromStr for HttpDate {
+ type Err = Error;
+ fn from_str(s: &str) -> Result<HttpDate, 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, f)
+ }
+}
+
+impl fmt::Display for HttpDate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
+ }
+}
+
+impl From<SystemTime> for HttpDate {
+ fn from(sys: SystemTime) -> HttpDate {
+ HttpDate(sys.into())
+ }
+}
+
+impl From<HttpDate> for SystemTime {
+ fn from(date: HttpDate) -> SystemTime {
+ SystemTime::from(date.0)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::HttpDate;
+
+ 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!(
+ "Mon, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
+ nov_07()
+ );
+ }
+
+ #[test]
+ fn test_rfc_850() {
+ assert_eq!(
+ "Monday, 07-Nov-94 08:48:37 GMT"
+ .parse::<HttpDate>()
+ .unwrap(),
+ nov_07()
+ );
+ }
+
+ #[test]
+ fn test_asctime() {
+ assert_eq!(
+ "Mon Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
+ nov_07()
+ );
+ }
+
+ #[test]
+ fn test_no_date() {
+ assert!("this-is-no-date".parse::<HttpDate>().is_err());
+ }
+}
diff --git a/third_party/rust/headers/src/util/iter.rs b/third_party/rust/headers/src/util/iter.rs
new file mode 100644
index 0000000000..03ed691ed6
--- /dev/null
+++ b/third_party/rust/headers/src/util/iter.rs
@@ -0,0 +1,11 @@
+pub trait IterExt: Iterator {
+ fn just_one(&mut self) -> Option<Self::Item> {
+ let one = self.next()?;
+ match self.next() {
+ Some(_) => None,
+ None => Some(one),
+ }
+ }
+}
+
+impl<T: Iterator> IterExt for T {}
diff --git a/third_party/rust/headers/src/util/mod.rs b/third_party/rust/headers/src/util/mod.rs
new file mode 100644
index 0000000000..07fddbfb6c
--- /dev/null
+++ b/third_party/rust/headers/src/util/mod.rs
@@ -0,0 +1,87 @@
+use HeaderValue;
+
+//pub use self::charset::Charset;
+//pub use self::encoding::Encoding;
+pub(crate) use self::entity::{EntityTag, EntityTagRange};
+pub(crate) use self::flat_csv::{FlatCsv, SemiColon};
+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::seconds::Seconds;
+pub(crate) use self::value_string::HeaderValueString;
+
+//mod charset;
+pub(crate) mod csv;
+//mod encoding;
+mod entity;
+mod flat_csv;
+mod fmt;
+mod http_date;
+mod iter;
+//mod quality_value;
+mod seconds;
+mod value_string;
+
+macro_rules! error_type {
+ ($name:ident) => {
+ #[doc(hidden)]
+ pub struct $name {
+ _inner: (),
+ }
+
+ impl ::std::fmt::Debug for $name {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.debug_struct(stringify!($name)).finish()
+ }
+ }
+
+ impl ::std::fmt::Display for $name {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.write_str(stringify!($name))
+ }
+ }
+
+ impl ::std::error::Error for $name {}
+ };
+}
+
+macro_rules! derive_header {
+ ($type:ident(_), name: $name:ident) => {
+ impl crate::Header for $type {
+ fn name() -> &'static ::http::header::HeaderName {
+ &::http::header::$name
+ }
+
+ fn decode<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i ::http::header::HeaderValue>,
+ {
+ ::util::TryFromValues::try_from_values(values).map($type)
+ }
+
+ fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
+ values.extend(::std::iter::once((&self.0).into()));
+ }
+ }
+ };
+}
+
+/// A helper trait for use when deriving `Header`.
+pub(crate) trait TryFromValues: Sized {
+ /// Try to convert from the values into an instance of `Self`.
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ Self: Sized,
+ I: Iterator<Item = &'i HeaderValue>;
+}
+
+impl TryFromValues for HeaderValue {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ values.next().cloned().ok_or_else(|| ::Error::invalid())
+ }
+}
diff --git a/third_party/rust/headers/src/util/seconds.rs b/third_party/rust/headers/src/util/seconds.rs
new file mode 100644
index 0000000000..a1a9194b0c
--- /dev/null
+++ b/third_party/rust/headers/src/util/seconds.rs
@@ -0,0 +1,67 @@
+use std::fmt;
+use std::time::Duration;
+
+use util::IterExt;
+use HeaderValue;
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub(crate) struct Seconds(Duration);
+
+impl Seconds {
+ pub(crate) fn from_val(val: &HeaderValue) -> Option<Self> {
+ let secs = val.to_str().ok()?.parse().ok()?;
+
+ 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 {
+ self.0.as_secs()
+ }
+}
+
+impl super::TryFromValues for Seconds {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ values
+ .just_one()
+ .and_then(Seconds::from_val)
+ .ok_or_else(::Error::invalid)
+ }
+}
+
+impl<'a> From<&'a Seconds> for HeaderValue {
+ fn from(secs: &'a Seconds) -> HeaderValue {
+ secs.0.as_secs().into()
+ }
+}
+
+impl From<Duration> for Seconds {
+ fn from(dur: Duration) -> Seconds {
+ debug_assert!(dur.subsec_nanos() == 0);
+ Seconds(dur)
+ }
+}
+
+impl From<Seconds> for Duration {
+ fn from(secs: Seconds) -> Duration {
+ secs.0
+ }
+}
+
+impl fmt::Debug for Seconds {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}s", self.0.as_secs())
+ }
+}
+
+impl fmt::Display for Seconds {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.0.as_secs(), f)
+ }
+}
diff --git a/third_party/rust/headers/src/util/value_string.rs b/third_party/rust/headers/src/util/value_string.rs
new file mode 100644
index 0000000000..865a3558b9
--- /dev/null
+++ b/third_party/rust/headers/src/util/value_string.rs
@@ -0,0 +1,93 @@
+use std::{
+ fmt,
+ str::{self, FromStr},
+};
+
+use bytes::Bytes;
+use http::header::HeaderValue;
+
+use super::IterExt;
+
+/// A value that is both a valid `HeaderValue` and `String`.
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub(crate) struct HeaderValueString {
+ /// Care must be taken to only set this value when it is also
+ /// a valid `String`, since `as_str` will convert to a `&str`
+ /// in an unchecked manner.
+ value: HeaderValue,
+}
+
+impl HeaderValueString {
+ pub(crate) fn from_val(val: &HeaderValue) -> Result<Self, ::Error> {
+ if val.to_str().is_ok() {
+ Ok(HeaderValueString { value: val.clone() })
+ } else {
+ Err(::Error::invalid())
+ }
+ }
+
+ pub(crate) fn from_string(src: String) -> Option<Self> {
+ // A valid `str` (the argument)...
+ let bytes = Bytes::from(src);
+ HeaderValue::from_maybe_shared(bytes)
+ .ok()
+ .map(|value| HeaderValueString { value })
+ }
+
+ pub(crate) fn from_static(src: &'static str) -> HeaderValueString {
+ // A valid `str` (the argument)...
+ HeaderValueString {
+ value: HeaderValue::from_static(src),
+ }
+ }
+
+ pub(crate) fn as_str(&self) -> &str {
+ // HeaderValueString is only created from HeaderValues
+ // that have validated they are also UTF-8 strings.
+ unsafe { str::from_utf8_unchecked(self.value.as_bytes()) }
+ }
+}
+
+impl fmt::Debug for HeaderValueString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(self.as_str(), f)
+ }
+}
+
+impl fmt::Display for HeaderValueString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(self.as_str(), f)
+ }
+}
+
+impl super::TryFromValues for HeaderValueString {
+ fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
+ where
+ I: Iterator<Item = &'i HeaderValue>,
+ {
+ values
+ .just_one()
+ .map(HeaderValueString::from_val)
+ .unwrap_or_else(|| Err(::Error::invalid()))
+ }
+}
+
+impl<'a> From<&'a HeaderValueString> for HeaderValue {
+ fn from(src: &'a HeaderValueString) -> HeaderValue {
+ src.value.clone()
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct FromStrError(());
+
+impl FromStr for HeaderValueString {
+ type Err = FromStrError;
+
+ fn from_str(src: &str) -> Result<Self, Self::Err> {
+ // A valid `str` (the argument)...
+ src.parse()
+ .map(|value| HeaderValueString { value })
+ .map_err(|_| FromStrError(()))
+ }
+}